From 16f659103e8fe20b202c3b70616df1a2a5e5db0d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 12:05:34 +0000 Subject: [PATCH 01/14] =?UTF-8?q?feat(qa):=20=E5=B9=B3=E5=8F=B0=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E6=A0=87=E5=87=86=E6=B5=8B=E8=AF=95=E6=B8=85=E5=8D=95?= =?UTF-8?q?=20=E2=80=94=20AI=20=E5=8F=AF=E6=89=A7=E8=A1=8C=E7=9A=84?= =?UTF-8?q?=E5=B8=B8=E5=A4=87=E5=8F=B0=E8=B4=A6=20(docs/qa/platform-checkl?= =?UTF-8?q?ist)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Release verification lived in one-off shapes — a checkbox issue per release (#3358) and a hand table (docs/plans/release-15.1-test-plan.md) — that could not accumulate: items were not reusable across releases, ticks had no revision to pin against, and every fixture gap a sweep found (#3408/#3409/#3415) was rediscovered from prose. This lands the standing, machine-readable successor: - areas/*.json — 9 areas, 32 items seeded from the #3358 evidence runs. Each item: clause-grained acceptance (clause + oracle + verify + evidence), declared fixtures with knownGaps, negative sides (both sides of every gate), false-positive traps, immutable . ids, revision + history for append/change governance, `automated` refs for the 🤖 lane. - README.md — item anatomy; append/change/retire lifecycle (append-only, never delete); release sweep = filter over the ledger (since:vN ∪ P0); relationship to dogfood-verification, @objectstack/verify, showcase coverage.ts, e2e-live, and why spec's inert TestScenarioSchema is deliberately not reused. - RUNNER.md — AI execution protocol: verdict vocabulary with first-class `partial`/`blocked`, oracle hierarchy (server truth > screenshot > DOM), fail requires reproduction ×2 + automation self-check + filed issue, trap vocabulary distilled from the #3358 retractions, durable run records under runs/ pinned to item revisions. - scripts/check-platform-checklist.mjs (+ lint.yml gate, house style): structural invariants only — unique immutable ids, revision == last history entry, every acceptance clause names its oracle. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01YD9f6FYyMraUWYeJf53V43 --- .github/workflows/lint.yml | 12 ++ docs/qa/platform-checklist/README.md | 121 ++++++++++++ docs/qa/platform-checklist/RUNNER.md | 127 ++++++++++++ .../areas/access-security.json | 120 ++++++++++++ .../platform-checklist/areas/api-backend.json | 109 +++++++++++ .../platform-checklist/areas/approvals.json | 184 ++++++++++++++++++ .../platform-checklist/areas/automation.json | 101 ++++++++++ .../platform-checklist/areas/dashboards.json | 84 ++++++++ docs/qa/platform-checklist/areas/i18n.json | 64 ++++++ .../areas/identity-auth.json | 68 +++++++ .../areas/platform-core.json | 107 ++++++++++ .../areas/records-forms.json | 177 +++++++++++++++++ docs/qa/platform-checklist/runs/README.md | 8 + package.json | 1 + scripts/check-platform-checklist.mjs | 165 ++++++++++++++++ 15 files changed, 1448 insertions(+) create mode 100644 docs/qa/platform-checklist/README.md create mode 100644 docs/qa/platform-checklist/RUNNER.md create mode 100644 docs/qa/platform-checklist/areas/access-security.json create mode 100644 docs/qa/platform-checklist/areas/api-backend.json create mode 100644 docs/qa/platform-checklist/areas/approvals.json create mode 100644 docs/qa/platform-checklist/areas/automation.json create mode 100644 docs/qa/platform-checklist/areas/dashboards.json create mode 100644 docs/qa/platform-checklist/areas/i18n.json create mode 100644 docs/qa/platform-checklist/areas/identity-auth.json create mode 100644 docs/qa/platform-checklist/areas/platform-core.json create mode 100644 docs/qa/platform-checklist/areas/records-forms.json create mode 100644 docs/qa/platform-checklist/runs/README.md create mode 100644 scripts/check-platform-checklist.mjs diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b48f8d01b7..ad55875c0c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -147,6 +147,18 @@ jobs: - name: ADR anchors (governed code names its decision) run: pnpm check:adr-anchors + # Standing platform test checklist (docs/qa/platform-checklist/): the + # AI-executed successor to the per-release checkbox issues (#3358) and + # hand tables (release-15.1-test-plan.md). Structural gate only: ids + # immutable+unique, revision matches history (a semantic edit that forgot + # to bump would silently re-validate old run verdicts), every acceptance + # clause names its oracle (an oracle-free clause is a tick-on-vibes + # invitation). The invariants travel in the script's header and failure + # output; authoring/execution contracts live next to the data (README.md / + # RUNNER.md). + - name: Platform test checklist ledger + run: pnpm check:platform-checklist + # #3280/#3290 org-identifier guard: `organizationId` is the blessed # developer-facing name for the caller's active org in hook/action bodies; # the `session.tenantId` alias was REMOVED in v11 (#3290). Keeps our own diff --git a/docs/qa/platform-checklist/README.md b/docs/qa/platform-checklist/README.md new file mode 100644 index 0000000000..ad1ad660e2 --- /dev/null +++ b/docs/qa/platform-checklist/README.md @@ -0,0 +1,121 @@ +# Platform test checklist — standing ledger + +A durable, machine-readable checklist of platform capabilities that an **AI agent +executes** against a running app (browser + API + CLI + build gates). It replaces the +one-off shapes release verification used before — a hand-written table per release +([docs/plans/release-15.1-test-plan.md](../../plans/release-15.1-test-plan.md)) and a +checkbox issue per release (#3358) — with one ledger that **accumulates across +releases**, supports append/change without losing history, and pins every tick to an +acceptance oracle and captured evidence. + +Validated in CI: `pnpm check:platform-checklist` +(`scripts/check-platform-checklist.mjs`). Execution protocol for agents: +[RUNNER.md](./RUNNER.md). Run records: [runs/](./runs/README.md). + +## Layout + +``` +docs/qa/platform-checklist/ + README.md ← this file: what an item is, how to append / change / retire + RUNNER.md ← how an AI runs the checklist accurately (verdicts, oracles, evidence) + areas/*.json ← the ledger, sharded by feature area (append here) + runs/ ← durable run records (one JSON per executed sweep) +``` + +Sharding by area keeps parallel edits conflict-free: two agents appending to different +areas never touch the same file, and slug ids (below) never collide the way +next-sequential numbers do. + +## Item anatomy + +```jsonc +{ + "id": "approvals.per-group-signoff", // "." — immutable, globally unique, never reused + "title": "Per-group sign-off (会签) needs one approval from EACH group", + "since": "v16", // release that introduced the capability + "status": "active", // active | draft | retired + "revision": 1, // bumps on any semantic edit + "priority": "P1", // P0 = release-gating smoke · P1 = core · P2 = extended + "surface": "browser", // browser | api | cli | build | mixed (the 15.1 plan's 🖥/🔌 lanes) + "personas": ["…"], // who the runner signs in as + "fixtures": { // what the environment must provide — the #1 cause of + "app": "showcase", // blocked runs in #3358 was missing fixtures, so they are + "requires": ["…"], // declared up front, and known gaps are recorded, not + "knownGaps": ["…"] // rediscovered every sweep + }, + "steps": ["…"], // how to exercise it + "acceptance": [ // ★ the acceptance criteria — one clause per assertable fact + { "clause": "what must hold", + "oracle": "api", // api | network | screenshot | dom | log | test | build + "verify": "how to consult the oracle, concretely", + "evidence": "what artifact the run must capture" } + ], + "negative": ["…"], // the other side of every gate (deny/absence cases) + "traps": ["hydration-race"], // known false-positive risks (vocabulary in RUNNER.md) + "automated": { "kind": "e2e", "ref": "path/to/pinning.test.ts" }, // set when a permanent test pins it + "blocked": { "by": "fixture", "ref": "#NNNN" }, // standing blocker, waive-with-a-reference + "source": ["#3358 §1"], // where the expectation comes from + "history": [ { "revision": 1, "date": "…", "change": "…", "ref": "#PR" } ] +} +``` + +Design notes: + +- **Acceptance is clause-grained**, because runs are clause-grained: the #3358 sweeps + repeatedly proved half an item and honestly left the box unchecked ("upload guard — + not ticking on the strength of a label"). Clause verdicts let a run record *which* + half passed instead of collapsing to one checkbox. +- **Every clause names its oracle.** The oracle hierarchy and the anti-false-positive + rules live in [RUNNER.md](./RUNNER.md); the validator only enforces that an oracle is + declared — an oracle-free clause is an invitation to tick on vibes. +- **`automated` is the 🤖 lane** of the 15.1 plan: once a permanent test pins an item, + runs may satisfy it by executing that test and citing its output as evidence, instead + of re-driving the browser. + +## Lifecycle — append, change, retire (never delete) + +- **Append** — add an item to its area file (or add a new area file). Pick an + `.` id that will still make sense in two years; ids are immutable and + never reused. New-capability items land with `since: v` in the same PR as + the capability, or from the release notes at release time. +- **Change** — edit the fields, bump `revision`, append a `history` entry saying what + changed and why. The revision matters because run records pin the revision they ran + against: a semantic edit silently re-validating old results is exactly what the + validator's revision/history check exists to stop. +- **Retire** — set `status: "retired"` + `retiredReason` (and `supersededBy` when a + successor exists). The row stays in the file; deleting rows destroys the history that + makes old run records interpretable. Retire when the capability is removed + (ADR-0049 enforce-or-remove) or the item is folded into a successor. +- **Blocked is not a lifecycle state** — it's a standing annotation (`blocked: {by, + ref}`) meaning "not runnable on stock fixtures today, tracked at ". The + showcase-side fixture gaps #3358 uncovered (#3408, #3409, #3415) each cost a sweep to + rediscover; recording the gap on the item is what stops that. + +## How a release sweep works + +A release no longer gets a hand-written checklist. The sweep for `vN` is a **filter +over this ledger**: `since == vN` (the new capabilities) ∪ all `P0` (the standing +smoke) ∪ any item whose `source` cites a PR in the release. The tracking issue for the +sweep links here and hosts discussion; results land as a run record under `runs/` +(plus findings filed as issues, one per failure). Item text, fixtures learned, and new +traps discovered flow **back into the ledger** as revisions — that is the accumulation +the one-off checklists never had. + +## Relationship to what already exists + +| System | Relationship | +|---|---| +| `.claude/skills/dogfood-verification` | **How** to boot/drive/verify without lying to yourself. RUNNER.md builds on it; the skill is not restated here. | +| `packages/verify` (`objectstack verify`) | Headless auto-derived proof engine (CRUD fidelity, RLS). Items delegate to it via `automated`/`oracle: "test"` rather than re-proving by hand. | +| `packages/qa/dogfood` golden tests | Permanent pins for historical regressions — the `automated.ref` target for API-lane items. | +| `examples/app-showcase/src/coverage.ts` | The ratchet that every spec variant is *demonstrable*. This ledger asserts the demonstrations *work when driven*. Fixture gaps found here should often be fixed there. | +| objectui `e2e/live/*` + ADR-0054 | The browser-lane automation and the UI-testability contract (stable locators, machine-readable async state) that makes browser oracles trustworthy. | +| `docs/plans/release-15.1-test-plan.md`, #3358 | The predecessors this generalizes. Their vocabulary (方式 lanes, 验证要点, 来源) maps to `surface`, `acceptance`, `source`. | + +**Deliberately not reused:** `packages/spec/src/qa/testing.zod.ts` +(`TestScenarioSchema`). Its action vocabulary is headless-API-only +(`create_record`/`api_call`/…) and cannot express browser clauses, visual oracles, +fixtures, or evidence requirements — and it currently has no runtime consumer (a +declared-but-inert surface under ADR-0049/0078, `qa` has no liveness ledger entry). +Adopting it here would have silently changed its meaning; if it gains a real executor +some day, `oracle: "test"` items can point at scenarios expressed in it. diff --git a/docs/qa/platform-checklist/RUNNER.md b/docs/qa/platform-checklist/RUNNER.md new file mode 100644 index 0000000000..21669ef948 --- /dev/null +++ b/docs/qa/platform-checklist/RUNNER.md @@ -0,0 +1,127 @@ +# Runner protocol — executing the checklist accurately + +How an AI agent runs [the platform checklist](./README.md) so that its verdicts can be +trusted. Every rule here was paid for: the #3358 sweeps produced three showcase-defect +discoveries, two real regressions — and also one self-inflicted false alarm and several +"ticked on a label" temptations. The protocol turns those lessons into mechanics. + +Prerequisite reading: the **dogfood-verification** skill +(`.claude/skills/dogfood-verification/SKILL.md`) — environment isolation (§0), the +build/runtime model incl. the vendored-console staleness trap (§2), and the +anti-false-positive rule (§3). This file assumes it and adds the checklist-specific +contract. + +## Verdicts + +Per **clause** (each acceptance entry gets exactly one): + +| verdict | meaning | +|---|---| +| `pass` | oracle consulted, expectation held, evidence captured | +| `fail` | oracle consulted, expectation violated, evidence captured, issue filed | +| `blocked` | could not consult the oracle — carries `{by: fixture\|environment\|dependency\|product-bug, ref}` | +| `skipped` | deliberately not attempted this run (out of scope) | + +Per **item**, derived — never hand-assigned: + +- `pass` — every clause passed; +- `partial` — some passed, none failed (the "proved half, left it unticked" state from + #3358, now first-class instead of a prose apology); +- `fail` — any clause failed; +- `blocked` / `not-run` — nothing consulted. + +**No verdict without evidence.** A clause with no captured artifact is `not-run`, not +`pass`. Evidence means: the API/network trace, the screenshot, the log excerpt, or the +test-run output the clause's `evidence` field names. + +## The accuracy rules + +1. **Oracle hierarchy** — server truth (`api`, `network`, `build`, `test`) outranks + `screenshot`, which outranks `dom`. A `dom` oracle may only be consulted **after** a + screenshot (or equivalent) confirms the surface rendered — post-navigation DOM dumps + return transitional emptiness and are the #1 source of fake "P0: feature missing" + findings (dogfood skill §3). +2. **`fail` is expensive, on purpose.** Before recording one: + - reproduce it **twice**, on fresh loads; + - run the *automation self-check*: could your own driving have caused this? + Coordinate-based clicks, React controlled-input fills, and shared browser tabs + have each produced convincing fake bugs (#3358 had to retract a "dead approve + button" that was a coordinate-click artifact — a ref-targeted click worked); + - check the `traps` field and rule each listed trap out; + - for console UI failures, confirm against current objectui source or a fresh build + — the vendored `/_console` bundle may be stale (skill §2); + - then file the issue and cite it in the run record. A `fail` without a filed issue + is not a completed verdict. +3. **Classify blockers honestly.** Missing seed/persona/fixture → `blocked(fixture)`, + and *record the gap on the item* (`fixtures.knownGaps` or `blocked`) so the next + sweep doesn't rediscover it. A defect in the fixture itself (seed silently failing, + as in #3408/#3415) is a **`fail` against the seed**, not a block — "nothing reports + this" was the actual bug. +4. **Both sides of every gate.** For any permission/visibility/feature gate, verify + presence for the entitled persona AND absence (or server-side rejection) for the + unentitled one. UI absence alone is a client courtesy; the server is the authority + (ADR-0057 D10) — where feasible, prove denial with a direct forged request. +5. **Severe findings are hypotheses.** "The whole surface is unreachable" gets + disproven-or-confirmed via screenshot + the server's own metadata before it is + written down (the golden rule of the dogfood skill). +6. **Don't re-prove what automation pins.** If `automated.ref` is set, run that test + and cite its output as the evidence; drive the browser only for what the pin doesn't + cover. The reverse also holds: when a sweep hand-proves something repeatedly, + propose promoting it to a permanent test and set `automated` in a revision. +7. **Verify pass for high-stakes claims.** For P0 `fail`s and any finding that would + ship or block a release: a second, independent agent re-derives the verdict from the + captured evidence alone (not from the first agent's narrative) before it is acted + on. Disagreement → re-run the item. + +### Trap vocabulary (`traps` field) + +| trap | what it fakes | counter | +|---|---|---| +| `hydration-race` | empty nav/list right after navigation | screenshot first; settle; then read DOM | +| `stale-console-bundle` | UI bug already fixed upstream in objectui | check against objectui HMR console / fresh build (skill §2) | +| `stale-dist` | src edits with no runtime effect | rebuild package + restart before judging | +| `automation-input` | dead buttons / empty submits caused by the driver | ref-targeted clicks; native setter + input/change events | +| `shared-browser-tab` | drifting origin, foreign drafts | pin absolute origin; own port/DB (skill §0) | +| `seed-data-thin` | features with nothing to show; silent seed rejections | check row counts vs built artifact; read boot log | +| `single-datapoint` | charts "render" but prove little | prefer multi-bucket fixtures; note weakness in evidence | +| `dispatcher-vs-hono-route` | route exists in unit tests, 404s on the real server | oracle = live server trace, never simulated dispatch | +| `wrong-panel` | feature looks missing on a sibling surface | item's `steps` name the exact surface; check it | +| `wrong-persona` | admin privileges mask a guard | run guard checks as the non-privileged persona | + +## Run records + +One JSON per executed sweep, committed as +`runs/YYYY-MM-DD-.json` (never edited afterwards — append a new record for a +re-run). Shape: + +```jsonc +{ + "run": "2026-08-07-v17-release-sweep", + "date": "2026-08-07", + "scope": "since:v17 + P0", // the filter that selected items + "app": "showcase", + "env": { + "framework": "", + "objectuiPin": "<.objectui-sha>", // stale-bundle honesty: record what the console was + "port": 3456, "db": "file:/tmp//data.db" + }, + "runner": "", + "results": [ + { + "id": "approvals.per-group-signoff", + "revision": 1, // ← the revision this verdict is valid for + "verdict": "pass", + "clauses": [ + { "clause": 0, "verdict": "pass", "evidence": "…what was captured, where…" } + ], + "issues": [], // filed failures / fixture gaps + "notes": "…" + } + ] +} +``` + +A run summary for humans may additionally go to `docs/audits/` (house convention, +date-prefixed) or the sweep's tracking issue — but the JSON record is the durable, +diffable truth, and it is what makes "this item last passed at revision N on date D" +answerable without archaeology. diff --git a/docs/qa/platform-checklist/areas/access-security.json b/docs/qa/platform-checklist/areas/access-security.json new file mode 100644 index 0000000000..09a4fd71ac --- /dev/null +++ b/docs/qa/platform-checklist/areas/access-security.json @@ -0,0 +1,120 @@ +{ + "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md.", + "area": "access-security", + "title": "Access, permissions, RLS/FLS, write-path guards", + "items": [ + { + "id": "access-security.rls-both-sides", + "title": "Row-level security: restricted member sees only their rows; admin sees all", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P0", + "surface": "api", + "personas": ["admin", "restricted member on an OWD-private object"], + "steps": [ + "as the restricted member, list an OWD-private object over the data API", + "as admin, list the same object", + "compare against the ground-truth row set" + ], + "acceptance": [ + { + "clause": "member reads return only owned/shared rows; admin reads return the full set — BOTH sides verified, absence and presence", + "oracle": "api", + "verify": "row-set comparison per persona; the auto-derived RLS proofs in @objectstack/verify (runRlsProofs) cover this app-agnostically — rls-consistent verdict required, rls-hole is a FAIL", + "evidence": "the two listings + the verify run verdict" + } + ], + "automated": { "kind": "verify", "ref": "packages/verify/src/rls.ts (objectstack verify)" }, + "source": ["packages/verify", "ADR-0057 D10"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — standing P0, delegating the systematic sweep to @objectstack/verify and keeping the persona spot-check manual", "ref": "#3358" }] + }, + { + "id": "access-security.write-path-guards", + "title": "Write-path guards: readonly strip, owner_id forge/transfer denied, bulk validation", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P0", + "surface": "api", + "personas": ["admin", "non-admin member (owner forge is only meaningful non-privileged)"], + "steps": [ + "REST-INSERT and UPDATE a readonly:true field; re-read the row", + "as the non-admin, attempt an insert forging another user's owner_id and an update transferring it", + "bulk-update a set that violates a validation rule" + ], + "acceptance": [ + { + "clause": "readonly fields are stripped on INSERT and UPDATE (value unchanged server-side)", + "oracle": "api", + "verify": "post-write reads show the readonly field untouched", + "evidence": "write payloads + post-write reads" + }, + { + "clause": "owner_id forge and transfer are denied for non-privileged users; bulk inserts stamp the current user", + "oracle": "api", + "verify": "forge/transfer attempts rejected; empty-owner bulk insert rows land stamped with the caller", + "evidence": "the rejections + stamped rows" + }, + { + "clause": "bulk update runs validation rules per row (the updateMany call-site gap of #3106 must not reproduce)", + "oracle": "api", + "verify": "a bulk update violating a rule is rejected/skipped per row, not silently applied", + "evidence": "the bulk response + row reads" + } + ], + "automated": { "kind": "dogfood", "ref": "packages/qa/dogfood/test/owner-anchor-and-bulk-writes.dogfood.test.ts (+ showcase-static-readonly)" }, + "traps": ["wrong-persona"], + "source": ["#3358 §9", "release-15.1 plan §A", "#3106"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — merged from #3358 §9 and the 15.1 A-group rows these dogfood tests pin", "ref": "#3358" }] + }, + { + "id": "access-security.record-access-explain", + "title": "Record-grained access explain shows per-layer attribution and a verdict", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["admin"], + "steps": [ + "in the access explain panel, pick user + object + record", + "read the layered result" + ], + "acceptance": [ + { + "clause": "the panel attributes access per layer (permission set → position → sharing → row rules) and renders a record.visible verdict naming the deciding layer", + "oracle": "screenshot", + "verify": "screenshot shows all layers with the verdict; spot-check the verdict against an actual read as that user (server truth)", + "evidence": "screenshot + the impersonated read" + } + ], + "traps": ["hydration-race"], + "source": ["#3358 §5"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358, adding the impersonated-read cross-check", "ref": "#3358" }] + }, + { + "id": "access-security.readonly-package-locks-studio", + "title": "A read-only package actually locks Studio editing surfaces", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "personas": ["admin"], + "steps": [ + "open the permission matrix / object designer on a read-only package" + ], + "acceptance": [ + { + "clause": "the read-only badge renders AND the controls are actually disabled — checkboxes/Save inert, edit affordances absent", + "oracle": "dom", + "verify": "after screenshot confirms render, assert disabled state on the controls (a badge alone is not a lock)", + "evidence": "screenshot + disabled-state DOM read" + } + ], + "source": ["#3358 §5"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }] + } + ] +} diff --git a/docs/qa/platform-checklist/areas/api-backend.json b/docs/qa/platform-checklist/areas/api-backend.json new file mode 100644 index 0000000000..4a02e51fca --- /dev/null +++ b/docs/qa/platform-checklist/areas/api-backend.json @@ -0,0 +1,109 @@ +{ + "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md.", + "area": "api-backend", + "title": "REST / batch / formula / build-time gates", + "items": [ + { + "id": "api-backend.batch-transactional-discovery", + "title": "transactionalBatch capability bit matches the mounted /batch behavior", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "steps": [ + "read client.capabilities.transactionalBatch (or the capabilities endpoint)", + "exercise /api/v1/batch: a well-formed atomic batch, then atomic:false" + ], + "acceptance": [ + { + "clause": "the discovery bit is true exactly when /batch is mounted and transaction-capable; atomic:false answers 400 BATCH_NOT_ATOMIC", + "oracle": "api", + "verify": "capability read + the two batch calls with their status/error codes", + "evidence": "the three responses" + } + ], + "source": ["#3358 §9"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }] + }, + { + "id": "api-backend.formula-gates", + "title": "Formula runtime fixes hold and date-arithmetic fails at build time", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "build", + "steps": [ + "evaluate the fixed runtime shapes against built @objectstack/formula: due_date == today(), cond ? x : null, floor/ceil", + "run os build on a config carrying date arithmetic (end - start + 1)" + ], + "acceptance": [ + { + "clause": "the runtime shapes evaluate correctly", + "oracle": "test", + "verify": "harness run against the BUILT package (not src) returns the expected values", + "evidence": "harness output" + }, + { + "clause": "date arithmetic is a build-time ERROR in os build — not a runtime surprise", + "oracle": "build", + "verify": "os build exits non-zero with the located formula error", + "evidence": "build output" + } + ], + "traps": ["stale-dist"], + "source": ["#3358 §9"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }] + }, + { + "id": "api-backend.enforce-or-remove-authoring-gates", + "title": "Removed/retired authoring keys fail at parse/build with located guidance", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "build", + "steps": [ + "author a config using a retired key (per the ADR-0087 registries / retiredKey tombstones)", + "parse/build it" + ], + "acceptance": [ + { + "clause": "the parse/build rejects the retired key with located guidance naming the replacement — never a silent strip for tombstoned keys", + "oracle": "build", + "verify": "ObjectSchema.create / os build throws the located error for a sampled retired key", + "evidence": "the error text" + } + ], + "negative": ["also sample one key documented as silently-stripped-by-design (e.g. pure display annotations) to confirm the strip stays deliberate and documented, not accidental"], + "source": ["#3358 §9", "ADR-0049", "ADR-0087"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }] + }, + { + "id": "api-backend.server-timing-admin-gated", + "title": "Server-Timing spans emit for admins only — on the server os dev actually runs", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "api", + "personas": ["admin", "non-admin"], + "steps": [ + "with OS_SERVER_TIMING enabled, request any route as admin with X-OS-Debug-Timing", + "repeat as a non-admin" + ], + "acceptance": [ + { + "clause": "admin responses carry auth/db/hooks/serialize spans; non-admin responses carry none — verified against the hono server os dev runs, because the #3361 regression lived exactly in the dispatcher-vs-hono seam while unit tests simulated the call and stayed green", + "oracle": "network", + "verify": "live response headers for both personas on the running dev server", + "evidence": "the two header dumps" + } + ], + "traps": ["dispatcher-vs-hono-route", "wrong-persona"], + "source": ["#3358 §9", "#3361"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; oracle pinned to the live server because of #3361", "ref": "#3358" }] + } + ] +} diff --git a/docs/qa/platform-checklist/areas/approvals.json b/docs/qa/platform-checklist/areas/approvals.json new file mode 100644 index 0000000000..4d958dd9e6 --- /dev/null +++ b/docs/qa/platform-checklist/areas/approvals.json @@ -0,0 +1,184 @@ +{ + "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md.", + "area": "approvals", + "title": "Approvals — quorum, per-group sign-off (会签), inbox", + "items": [ + { + "id": "approvals.per-group-signoff", + "title": "Per-group sign-off (会签) needs one approval from EACH group", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["approver holding exactly one group (e.g. manager)", "second approver holding the other group (e.g. finance/auditor)"], + "fixtures": { + "app": "showcase", + "requires": [ + "a launched per_group request whose two groups resolve to two DISTINCT users (showcase: ExpenseSignoffFlow on EXP-2001, Ada Auditor holds auditor — seeded since #3409)" + ] + }, + "steps": [ + "as the single-group approver, open the request in the approval inbox", + "record the pending slate and per-group progress from the drawer", + "approve, then re-read the request state over the API" + ], + "acceptance": [ + { + "clause": "one group's approval satisfies that group and drops it from the pending slate, but the request stays pending on the remaining group", + "oracle": "api", + "verify": "before: status=pending, both groups 0/1; after the approve: status STILL pending, approver's group satisfied, other group's user still listed as pending", + "evidence": "before/after API reads of the request (status + pending approvers)" + }, + { + "clause": "per-group progress is server-computed, not a client guess", + "oracle": "screenshot", + "verify": "the drawer renders per-group tick badges ('N of M groups', group chips with counts) that match the API state", + "evidence": "drawer screenshot alongside the API read" + } + ], + "negative": [ + "contrast case: a request whose slots all resolve to ONE user finalizes on a single decision — confirm per_group did NOT (that contrast is the proof the behavior differs)" + ], + "traps": ["automation-input"], + "source": ["#3358 §1", "#3409", "#3411"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the #3358 evidence run (decisive oracle: group drops but request stays pending)", "ref": "#3358" }] + }, + { + "id": "approvals.quorum-m-of-n", + "title": "M-of-N quorum approves at the threshold; one rejection vetoes", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["three DISTINCT users each holding one of the approver positions"], + "fixtures": { + "app": "showcase", + "requires": ["a quorum (minApprovals: 2) request whose three position approvers resolve to three distinct users"], + "knownGaps": [ + "showcase admin holds manager+finance+legal, so the slate collapses to one person and the runtime clamps 2-of-3 to 1-of-1 — M-of-N is not demonstrable on stock seeds (#3358); needs a dedicated fixture or seed change" + ] + }, + "blocked": { "by": "fixture", "ref": "#3358 (quorum slate collapses onto the admin — showcase design call pending)" }, + "steps": [ + "as approver 1, approve — confirm the request stays pending (1 of 2)", + "as approver 2, approve — confirm the request finalizes and remaining tasks close", + "on a second request: as any approver, reject — confirm immediate veto" + ], + "acceptance": [ + { + "clause": "the request approves exactly when minApprovals distinct approvals are recorded", + "oracle": "api", + "verify": "status transitions pending→pending→approved across the two approvals; approval action rows count 2 distinct actors", + "evidence": "API reads after each decision" + }, + { + "clause": "a single rejection vetoes even with quorum-1 approvals already recorded", + "oracle": "api", + "verify": "status flips to rejected on the first reject; no further tasks remain actionable", + "evidence": "API read after the reject" + } + ], + "traps": ["seed-data-thin"], + "source": ["#3358 §1"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; carried the fixture blocker forward explicitly", "ref": "#3358" }] + }, + { + "id": "approvals.inbox-metadata-actions", + "title": "Inbox actions are metadata-driven and gated by the viewer's relationship", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["approver who is NOT the submitter", "same user viewing a request they submitted"], + "fixtures": { + "app": "showcase", + "requires": ["requests with a real (non-null) submitter — seeded since #3411 (invoice submitted by admin, others by a no-position persona)"] + }, + "steps": [ + "as the same viewer, open one request submitted by someone else and one they submitted themselves", + "record the rendered action set on each" + ], + "acceptance": [ + { + "clause": "approver-side actions (approve/reject/reassign/send-back/request-info) render from declared metadata on the other-submitter request", + "oracle": "dom", + "verify": "after confirming render via screenshot, read the action buttons from the drawer DOM and match the declared action set", + "evidence": "screenshot + DOM action list" + }, + { + "clause": "submitter-side actions (send-reminder, recall) appear ONLY on the viewer's own request", + "oracle": "dom", + "verify": "own-request drawer shows the two extra actions; other-submitter drawer does not", + "evidence": "side-by-side action lists for the two requests" + }, + { + "clause": "each action executes its REST route (spot-check one: the decision commits)", + "oracle": "network", + "verify": "clicking an action issues its API call and the state change round-trips", + "evidence": "network trace of the action call" + } + ], + "traps": ["automation-input", "hydration-race"], + "source": ["#3358 §1", "#3411"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the #3358 evidence run (7-action table proven after #3411 stamped real submitters)", "ref": "#3358" }] + }, + { + "id": "approvals.viewer-gating-submitter-side", + "title": "A submitter who is not an approver sees no approver buttons", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["submitter holding NO approver position on their own pending request"], + "fixtures": { + "app": "showcase", + "requires": ["one pending request routed to a position its submitter does not hold"], + "knownGaps": ["stock seeds route every request to positions the admin holds, so the admin is an approver on all of them (#3358) — needs one request addressed away from the signed-in persona"] + }, + "blocked": { "by": "fixture", "ref": "#3358 (needs a request routed to a position the viewing submitter does not hold)" }, + "steps": [ + "sign in as the no-position submitter", + "open their own pending request from the '我发起的' tab" + ], + "acceptance": [ + { + "clause": "approve/reject buttons do not render for the submitter, and the gate is server-side, not a client heuristic", + "oracle": "api", + "verify": "the drawer omits approver actions AND a forged direct POST of the decision route as the submitter is rejected (test both sides of the gate)", + "evidence": "screenshot of the drawer + the rejected API call" + } + ], + "traps": ["hydration-race"], + "source": ["#3358 §1", "ADR-0057 D10 (server is the authoritative visibility gate)"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; upgraded the oracle from DOM-only to both-sides (UI absence + server rejection)", "ref": "#3358" }] + }, + { + "id": "approvals.notification-deep-link", + "title": "An approval notification deep-links straight into the request drawer", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "personas": ["any pending approver"], + "steps": [ + "click an approval bell notification (or open the ?request= URL it carries) on a fresh page load" + ], + "acceptance": [ + { + "clause": "the ?request= URL opens the request drawer directly on a cold load", + "oracle": "screenshot", + "verify": "fresh navigation to .../approvals?request= renders the drawer for that exact request (verify twice on fresh loads)", + "evidence": "screenshot with the URL visible" + } + ], + "traps": ["hydration-race"], + "source": ["#3358 §1"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358 (verified twice on fresh loads)", "ref": "#3358" }] + } + ] +} diff --git a/docs/qa/platform-checklist/areas/automation.json b/docs/qa/platform-checklist/areas/automation.json new file mode 100644 index 0000000000..6d7b9e0860 --- /dev/null +++ b/docs/qa/platform-checklist/areas/automation.json @@ -0,0 +1,101 @@ +{ + "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md.", + "area": "automation", + "title": "Automation — flows, triggers, roll-ups", + "items": [ + { + "id": "automation.flow-run-step-nesting", + "title": "Flow Runs render loop/region iterations as a nested execution tree", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["admin"], + "fixtures": { "app": "showcase", "requires": ["a loop flow runnable on demand (e.g. showcase_batch_reminders with a small list input)"] }, + "steps": [ + "run the loop flow with a 3-item input", + "open the FLOW DESIGNER's Runs panel for that run (not the developer Flow Runs page)" + ], + "acceptance": [ + { + "clause": "the run's steps carry parent/iteration/region tags and the designer Runs panel renders them as a nested tree (per-iteration children under the loop node), not a flat list", + "oracle": "api", + "verify": "the run detail API shows parent=loop node, iter=0..2, region=loop-body per body step; the panel screenshot shows the ITERATION-nested rendering", + "evidence": "run-detail API read + Runs panel screenshot" + } + ], + "traps": ["wrong-panel"], + "source": ["#3358 §2 — 'the developer Flow Runs page renders steps flat; looking there alone reads as a miss'"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358, encoding its wrong-panel lesson as a trap", "ref": "#3358" }] + }, + { + "id": "automation.time-relative-trigger", + "title": "Time-relative flow triggers author first-class and fire per matching record", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": ["admin"], + "steps": [ + "author a flow start node with timeRelative (offsetDays or withinDays) in the designer", + "confirm the boot banner binds it; where feasible, stage a record whose date matches and confirm the daily sweep fires" + ], + "acceptance": [ + { + "clause": "the designer renders the first-class timeRelative panel (sweep object / date field / offset days as a numberList editor, not raw JSON)", + "oracle": "screenshot", + "verify": "designer panel screenshot shows the dedicated fields", + "evidence": "screenshot" + }, + { + "clause": "the flow binds at boot and a matching record produces a run on the sweep", + "oracle": "log", + "verify": "Flows banner counts it bound as time_relative; a staged matching record yields a run row after the sweep", + "evidence": "banner line + run list read" + } + ], + "traps": ["seed-data-thin"], + "source": ["#3358 §2"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }] + }, + { + "id": "automation.rollup-summary-filter", + "title": "Filtered roll-up summaries recompute only through their own filter", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": ["a parent with several filtered roll-ups in different filter shapes (showcase_expense_report: equality / boolean / operator / unfiltered)"], + "knownGaps": ["the visual filter-editor half needs a summary field in a WRITABLE package — the showcase ships read-only, so Studio editing is not reachable on stock fixtures (#3358)"] + }, + "steps": [ + "flip one child row's filtered dimension over REST (e.g. status approved→rejected)", + "re-read every roll-up on the parent and independently recompute each from the child rows" + ], + "acceptance": [ + { + "clause": "only the roll-ups whose filter the edit touched move; every value matches an independent recomputation from the child rows", + "oracle": "api", + "verify": "before/after parent reads: touched roll-ups change to the recomputed value; unfiltered and untouched-filter roll-ups stay exactly put (the part a naive recompute-everything gets wrong)", + "evidence": "before/after value table with the independent recomputation" + }, + { + "clause": "the child-row filter is settable via the visual editor on a writable package", + "oracle": "screenshot", + "verify": "edit a summary field's filter in Studio on a writable package and confirm the persisted metadata", + "evidence": "editor screenshot + saved metadata read" + } + ], + "blocked": { "by": "fixture", "ref": "#3358 (editor half needs a writable-package summary field fixture)" }, + "traps": ["seed-data-thin"], + "source": ["#3358 §2 (recompute half proven; editor half explicitly left unticked)"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; clause 1 runnable today, clause 2 carries the fixture blocker", "ref": "#3358" }] + } + ] +} diff --git a/docs/qa/platform-checklist/areas/dashboards.json b/docs/qa/platform-checklist/areas/dashboards.json new file mode 100644 index 0000000000..14e936eee0 --- /dev/null +++ b/docs/qa/platform-checklist/areas/dashboards.json @@ -0,0 +1,84 @@ +{ + "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md.", + "area": "dashboards", + "title": "Dashboards, reports, analytics", + "items": [ + { + "id": "dashboards.strict-widget-rejects-stray-keys", + "title": "A widget authored with a stray/legacy key fails loud, never renders blank", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["admin"], + "steps": [ + "author a dashboard widget carrying a legacy key (e.g. categoryField) or a hallucinated key", + "load the dashboard / run the parse" + ], + "acceptance": [ + { + "clause": "the parse rejects the widget with a loud error NAMING the offending key and pointing at the dataset shape — the old failure mode (silently renders nothing) must not reproduce", + "oracle": "log", + "verify": "the error names the key and cites the expected dataset+dimensions+values shape (ADR-0021)", + "evidence": "the error text" + } + ], + "negative": ["this item IS a negative test — its pass condition is the rejection; a blank-but-no-error render is a FAIL"], + "source": ["#3358 §3 (four stray-key variants all rejected with the named error)"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }] + }, + { + "id": "dashboards.chart-first-paint", + "title": "Charts draw on first paint — no blank-until-resize", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "steps": [ + "cold-load a dashboard with bar charts", + "screenshot immediately after render settles, WITHOUT resizing the window" + ], + "acceptance": [ + { + "clause": "bars/axes are drawn on the first paint", + "oracle": "screenshot", + "verify": "the first-paint screenshot shows a real chart (recharts SVG with bars), not an empty plot area", + "evidence": "screenshot" + } + ], + "traps": ["single-datapoint", "hydration-race"], + "source": ["#3358 §3 — note its caveat: with thin seeds every widget draws one data point, so the tick 'says less than it looks'; prefer multi-bucket fixtures"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358 with the thin-seed caveat attached", "ref": "#3358" }] + }, + { + "id": "dashboards.drill-through-range", + "title": "Report drill-through scopes the drilled list to the exact bucket range", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "fixtures": { + "app": "showcase", + "requires": ["a date-bucketed report with MULTIPLE buckets to drill (blocked historically by #3415 — seed validation silently rejected 4 of 5 projects, leaving single-point charts)"] + }, + "steps": [ + "drill a date-bucket cell (e.g. a quarter)", + "capture the drilled list's query" + ], + "acceptance": [ + { + "clause": "the drilled list is scoped to exactly that time range — not a superset", + "oracle": "network", + "verify": "the drill request carries the bucket's from/to bounds and the row count matches the bucket's aggregate", + "evidence": "the captured query + count comparison" + } + ], + "traps": ["seed-data-thin", "single-datapoint"], + "source": ["#3358 §3", "#3415"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; runnable once multi-bucket seeds exist", "ref": "#3358" }] + } + ] +} diff --git a/docs/qa/platform-checklist/areas/i18n.json b/docs/qa/platform-checklist/areas/i18n.json new file mode 100644 index 0000000000..a57c96d6a6 --- /dev/null +++ b/docs/qa/platform-checklist/areas/i18n.json @@ -0,0 +1,64 @@ +{ + "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md.", + "area": "i18n", + "title": "Internationalization", + "items": [ + { + "id": "i18n.notification-localized-and-clears", + "title": "zh-CN notification is localized, deep-links localized, and mark-as-read clears", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["zh-CN workspace user receiving a collab.assignment notification"], + "steps": [ + "trigger an assignment notification on a zh-CN workspace under os dev/standalone", + "open the bell, click through, mark as read", + "capture the mark-read request" + ], + "acceptance": [ + { + "clause": "title and detail page render in the recipient locale", + "oracle": "screenshot", + "verify": "bell entry + detail page screenshots in zh-CN", + "evidence": "screenshots" + }, + { + "clause": "mark-as-read actually clears the unread state — the notifications REST routes must be mounted on the server actually serving os dev (the #3362 dispatcher-only registration made the console 404 here while unit tests stayed green)", + "oracle": "network", + "verify": "POST /api/v1/notifications/read returns 2xx on the running server and a fresh unread-count read drops", + "evidence": "the network trace + before/after unread count" + } + ], + "traps": ["dispatcher-vs-hono-route"], + "source": ["#3358 §7", "#3362", "#3354"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; the #3362 route-seam regression is the reason the oracle is a live-server network trace, never a unit test", "ref": "#3358" }] + }, + { + "id": "i18n.studio-follows-app-locale", + "title": "Studio follows the in-app locale switch — no mixed-language session", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "personas": ["admin"], + "steps": [ + "switch the in-app locale", + "navigate into Studio / metadata-admin surfaces" + ], + "acceptance": [ + { + "clause": "Studio surfaces render in the switched locale (including relative dates); no mixed-language session", + "oracle": "screenshot", + "verify": "post-switch Studio screenshots show the target locale consistently", + "evidence": "screenshots" + } + ], + "traps": ["stale-console-bundle"], + "source": ["#3358 §7"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }] + } + ] +} diff --git a/docs/qa/platform-checklist/areas/identity-auth.json b/docs/qa/platform-checklist/areas/identity-auth.json new file mode 100644 index 0000000000..a0a8e11e18 --- /dev/null +++ b/docs/qa/platform-checklist/areas/identity-auth.json @@ -0,0 +1,68 @@ +{ + "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md.", + "area": "identity-auth", + "title": "Auth, login, identity", + "items": [ + { + "id": "identity-auth.sso-enforced-first-paint", + "title": "ssoEnforced login honors SSO on first paint — no password-wall flash", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "fixtures": { "app": "any", "requires": ["an env with ssoEnforced configured, cold-started"] }, + "steps": [ + "cold-start the server with ssoEnforced, open the login page fresh", + "capture the FIRST paint (screenshot as early as render settles)" + ], + "acceptance": [ + { + "clause": "the first paint honors ssoEnforced — the password form never flashes before the SSO redirect/button", + "oracle": "screenshot", + "verify": "first-paint screenshot shows the SSO surface; the settings API confirms the flag the client is honoring", + "evidence": "screenshot + /api/settings read" + }, + { + "clause": "a hung sign-in recovers via the watchdog rather than stranding the page", + "oracle": "screenshot", + "verify": "after the watchdog window, the page offers a retry path (not a spinner forever)", + "evidence": "post-window screenshot" + } + ], + "traps": ["hydration-race"], + "source": ["#3358 §6"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }] + }, + { + "id": "identity-auth.phone-signin-surfaces", + "title": "A phone-based user's number shows across the identity surfaces", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": ["a seeded phone persona (Mei Phone — the seed itself failed silently until #3408; if she is absent, that regression has returned)"] + }, + "steps": [ + "create (or locate the seeded) phone-based user", + "check the create-result dialog, the All Users list, and the record detail" + ], + "acceptance": [ + { + "clause": "the phone number renders in all three surfaces", + "oracle": "screenshot", + "verify": "screenshots of dialog, list row, and detail highlights each showing the number", + "evidence": "the three screenshots" + } + ], + "negative": ["if the seeded persona is missing, verdict is FAIL on the seed (regression of #3408), not blocked — the boot log will carry the insert error"], + "traps": ["seed-data-thin"], + "source": ["#3358 §6", "#3408"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; encoded the #3408 silent-seed-failure as an explicit negative", "ref": "#3358" }] + } + ] +} diff --git a/docs/qa/platform-checklist/areas/platform-core.json b/docs/qa/platform-checklist/areas/platform-core.json new file mode 100644 index 0000000000..b998f86bb9 --- /dev/null +++ b/docs/qa/platform-checklist/areas/platform-core.json @@ -0,0 +1,107 @@ +{ + "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md.", + "area": "platform-core", + "title": "Platform core — boot, health, console shell", + "items": [ + { + "id": "platform-core.boot-health", + "title": "Showcase boots clean: health 200, no degraded startup banners", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P0", + "surface": "mixed", + "preconditions": [ + "isolated run per dogfood-verification §0: own free port, own file DB (--seed-admin -d file:/tmp//data.db)" + ], + "steps": [ + "boot examples/app-showcase via `objectstack dev --ui --seed-admin` on an isolated port", + "poll GET /api/v1/health until 200", + "read the full boot log" + ], + "acceptance": [ + { + "clause": "GET /api/v1/health returns 200 within the boot window", + "oracle": "api", + "verify": "curl -s -o /dev/null -w '%{http_code}' http://localhost:/api/v1/health → 200", + "evidence": "the curl output" + }, + { + "clause": "the `Flows:` startup banner reports no ⚠ misauthored flows and no ERROR lines appear during boot", + "oracle": "log", + "verify": "grep the boot log for '⚠' in the Flows banner and for ERROR-level lines; seed rejections count as failures (see #3415 — SeedLoader rejections were silent)", + "evidence": "the grepped log excerpt" + }, + { + "clause": "the console shell is served at /_console/ and the seeded app resolves", + "oracle": "api", + "verify": "GET /api/v1/meta/app?id= returns the merged app/nav metadata", + "evidence": "response status + top-level keys" + } + ], + "traps": ["seed-data-thin"], + "source": ["dogfood-verification skill §0–§1", "#3415"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — standing P0 smoke distilled from the dogfood boot protocol", "ref": "#3358" }] + }, + { + "id": "platform-core.console-login", + "title": "Seeded admin can sign in through the console login page", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P0", + "surface": "browser", + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "steps": [ + "open /_console/ in the browser", + "sign in with the seeded admin credentials", + "reload the page" + ], + "acceptance": [ + { + "clause": "login succeeds and lands in the console shell", + "oracle": "screenshot", + "verify": "post-login screenshot shows the app shell (nav + header), not the login form", + "evidence": "screenshot" + }, + { + "clause": "the session survives a reload", + "oracle": "network", + "verify": "after reload, authed API calls return 200 (no redirect back to login)", + "evidence": "network trace of the first authed request after reload" + } + ], + "negative": [ + "a wrong password is rejected with a visible error, not a silent no-op" + ], + "traps": ["automation-input"], + "source": ["dogfood-verification skill §4 (React controlled inputs)"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial", "ref": "#3358" }] + }, + { + "id": "platform-core.nav-surfaces-render", + "title": "Every showcase nav surface renders without page errors", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P0", + "surface": "browser", + "steps": [ + "walk every nav destination in the showcase app (pages, list views, dashboards, reports, view gallery)", + "on each: wait for render, then check for pageerror / empty main / placeholder leaks" + ], + "acceptance": [ + { + "clause": "no surface throws a pageerror, renders an empty
, or leaks a 'no actions configured' placeholder; chart surfaces draw a real SVG", + "oracle": "test", + "verify": "run the pinned suite: pnpm -C examples/app-showcase test:smoke (SURFACES array in e2e/showcase-smoke.spec.ts)", + "evidence": "test run output" + } + ], + "automated": { "kind": "e2e", "ref": "examples/app-showcase/e2e/showcase-smoke.spec.ts" }, + "traps": ["hydration-race", "single-datapoint"], + "source": ["examples/app-showcase/e2e/showcase-smoke.spec.ts", ".github/workflows/showcase-smoke.yml"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — wraps the existing automated smoke as a checklist row so runs report it alongside manual items", "ref": "#3358" }] + } + ] +} diff --git a/docs/qa/platform-checklist/areas/records-forms.json b/docs/qa/platform-checklist/areas/records-forms.json new file mode 100644 index 0000000000..35927afbf5 --- /dev/null +++ b/docs/qa/platform-checklist/areas/records-forms.json @@ -0,0 +1,177 @@ +{ + "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md.", + "area": "records-forms", + "title": "Records, lists, detail pages, forms", + "items": [ + { + "id": "records-forms.crud-roundtrip", + "title": "Create → read → update → delete a record through the console UI", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P0", + "surface": "browser", + "personas": ["admin"], + "steps": [ + "on a seeded showcase object, create a record through the New form", + "open its detail page; edit a field; save", + "delete it from the row/detail actions", + "after each mutation, re-read the row over the data API" + ], + "acceptance": [ + { + "clause": "each UI mutation is reflected in the server row (the API read is the oracle, not the grid repaint)", + "oracle": "api", + "verify": "GET /api/v1/data/ after each step shows the created values, the edit, and finally the row gone", + "evidence": "the three API reads" + }, + { + "clause": "the list view reflects the changes after refresh without stale rows", + "oracle": "screenshot", + "verify": "post-delete screenshot of the list shows the row absent", + "evidence": "screenshot" + } + ], + "traps": ["hydration-race", "automation-input"], + "source": ["dogfood-verification skill §3"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — standing P0 smoke", "ref": "#3358" }] + }, + { + "id": "records-forms.master-detail-atomic-save", + "title": "Master + line items save as one atomic batch", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["admin"], + "fixtures": { "app": "showcase", "requires": ["a master-detail pair with an inline child grid (e.g. expense report + lines)"] }, + "steps": [ + "edit the master and several child lines in one form session, save once", + "repeat with one child line made deliberately invalid, save" + ], + "acceptance": [ + { + "clause": "the happy-path save lands master + children in one batch request", + "oracle": "network", + "verify": "the save issues a single batch call (not N sequential writes); all rows readable afterwards", + "evidence": "network trace of the save" + }, + { + "clause": "a failing child aborts the WHOLE save — no partial writes", + "oracle": "api", + "verify": "after the failed save, API reads show master and siblings unchanged", + "evidence": "API reads proving no row moved" + } + ], + "negative": ["the invalid-child case above IS the negative side — it must not partially commit"], + "traps": ["automation-input"], + "source": ["#3358 §4"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }] + }, + { + "id": "records-forms.related-list-server-pagination", + "title": "Related lists page on the server, never fetch every child row", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["admin"], + "fixtures": { "app": "showcase", "requires": ["a parent record with more children than one page (e.g. Northwind account with 26 contacts)"] }, + "steps": [ + "open the parent detail page's related list", + "click next-page; capture the data requests" + ], + "acceptance": [ + { + "clause": "paging issues server-side $top/$skip requests scoped by the parent filter; the full child set is never fetched", + "oracle": "network", + "verify": "page 1 = top=N&filter=[parent]; next page = top=N&skip=N; no request without top", + "evidence": "the captured request URLs + the pager reading 'page 2 of M'" + } + ], + "traps": ["hydration-race"], + "automated": null, + "source": ["#3358 §4 (evidence run captured exactly this trace)"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the #3358 evidence run", "ref": "#3358" }] + }, + { + "id": "records-forms.action-param-widgets", + "title": "Action params render their real widgets, not text inputs", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["admin"], + "fixtures": { "app": "showcase", "requires": ["the Action Param Gallery action on the Field Zoo row menu"] }, + "steps": [ + "run the gallery action; wait for the dialog to render (screenshot first)", + "read each param's rendered control from the dialog DOM" + ], + "acceptance": [ + { + "clause": "each param type maps to its real widget: date→input[type=date], color→input[type=color], richtext→editor, select→picker, file/image→input[type=file] with accept/multiple as declared, autonumber→read-only server-assigned", + "oracle": "dom", + "verify": "after screenshot confirms render, enumerate controls and match the declared param types; accept/multiple must be real DOM attributes", + "evidence": "screenshot + the param→control table" + } + ], + "negative": ["maxSize is enforced in JS, not as a DOM attribute — do NOT claim it from attribute absence; it needs an oversized-upload attempt to verify"], + "traps": ["hydration-race"], + "source": ["#3358 §4 (evidence table)", "#3393"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358, keeping its maxSize caveat as a negative-side note", "ref": "#3358" }] + }, + { + "id": "records-forms.upload-guard-blocks-confirm", + "title": "Confirm stays disabled while a file param is still uploading", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "personas": ["admin"], + "steps": [ + "open an action dialog with a file param; start a real upload large/slow enough to catch mid-flight", + "attempt to confirm while the upload is in progress" + ], + "acceptance": [ + { + "clause": "the Confirm control is actually disabled mid-upload and enables on completion — proven by catching a real upload in flight, not by reading the helper label", + "oracle": "dom", + "verify": "during the in-flight window the button carries disabled state and clicking issues no request; after completion the confirm succeeds", + "evidence": "mid-flight screenshot + network trace showing no premature submit" + } + ], + "traps": ["automation-input"], + "source": ["#3358 §4 ('leaving it unticked on the strength of a label')", "ADR-0059"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — codifies the #3358 refusal to tick from a label into the oracle itself", "ref": "#3358" }] + }, + { + "id": "records-forms.import-wizard-encoding-and-hints", + "title": "CSV import: GBK decode, required-field hint, legacy-fallback notice", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "steps": [ + "run the pinned unit suites (they feed real GBK bytes and assert the exact decode, the disabled-Next hint, and the visible degrade notice)", + "optionally spot-check the wizard visually past the native file picker" + ], + "acceptance": [ + { + "clause": "all three clauses are pinned by targeted tests in objectui", + "oracle": "test", + "verify": "pnpm --filter @object-ui/plugin-grid exec vitest run src/importParsers.test.ts src/__tests__/importMissingRequiredHint.test.tsx src/__tests__/importLegacyReferenceGuard.test.tsx", + "evidence": "test run output" + } + ], + "automated": { "kind": "unit", "ref": "objectui: packages/plugin-grid/src/importParsers.test.ts (+ importMissingRequiredHint, importLegacyReferenceGuard)" }, + "traps": ["automation-input"], + "source": ["#3358 §4 (ticked on test evidence — the native file picker hand-off is deliberately not automated)"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358's test-evidence resolution", "ref": "#3358" }] + } + ] +} diff --git a/docs/qa/platform-checklist/runs/README.md b/docs/qa/platform-checklist/runs/README.md new file mode 100644 index 0000000000..343324327d --- /dev/null +++ b/docs/qa/platform-checklist/runs/README.md @@ -0,0 +1,8 @@ +# Run records + +One JSON file per executed checklist sweep, named `YYYY-MM-DD-.json`, +**append-only**: a record is never edited after landing — a re-run is a new record. +The record shape and the verdict rules are defined in [../RUNNER.md](../RUNNER.md); +verdicts are only meaningful next to the item `revision` they ran against. + +Nothing here yet: the first sweep against the standing ledger lands the first record. diff --git a/package.json b/package.json index 1693f4f9ba..302204028b 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "check:skill-frame-sync": "node scripts/check-skill-frame-sync.mjs --self-test && node scripts/check-skill-frame-sync.mjs", "check:skill-frame-freshness": "node scripts/check-skill-frame-freshness.mjs --self-test && node scripts/check-skill-frame-freshness.mjs", "check:adr-anchors": "node scripts/check-adr-anchors.mjs", + "check:platform-checklist": "node scripts/check-platform-checklist.mjs", "check:org-identifier": "node scripts/check-org-identifier.mjs", "check:authz-resolver": "node scripts/check-single-authz-resolver.mjs --self-test && node scripts/check-single-authz-resolver.mjs", "check:slot-lookup": "node scripts/check-slot-lookup-ratchet.mjs", diff --git a/scripts/check-platform-checklist.mjs b/scripts/check-platform-checklist.mjs new file mode 100644 index 0000000000..677271d38b --- /dev/null +++ b/scripts/check-platform-checklist.mjs @@ -0,0 +1,165 @@ +#!/usr/bin/env node +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. +// +// check-platform-checklist — keep the standing platform test checklist +// (docs/qa/platform-checklist/) machine-readable, append-only and honest. +// +// ## The failure this exists for +// +// Release verification used to live in one-off surfaces: a hand-written table +// per release (docs/plans/release-15.1-test-plan.md) and a checkbox issue per +// release (#3358). Both worked once and then rotted: items could not be reused +// across releases, results were checkboxes with no revision to pin them to, and +// every fixture gap the run discovered (#3408 phone persona never seeded, #3409 +// per-group sign-off never launched, #3415 four of five projects silently +// rejected by seed validation) had to be rediscovered from prose. The standing +// checklist replaces those one-offs with a durable ledger; this gate keeps the +// ledger's invariants from decaying the same way. +// +// ## What this checks (deliberately dumb, presence-level — house ledger style) +// +// - every docs/qa/platform-checklist/areas/*.json parses and its `area` +// matches its filename; +// - every item carries the required fields with sane enum values; +// - ids are `.`, globally unique, and — append-only discipline — +// never removed: ids retired from service stay in the file with +// `status: "retired"` (this check cannot see deletions; the README makes +// removal a review-time offence, and `supersededBy` targets must resolve); +// - `revision` matches the last `history` entry, so a semantic edit that +// forgets to bump the revision (silently re-validating old run results) +// fails here; +// - active items have at least one acceptance clause, and every clause names +// its oracle — a clause with no oracle is an invitation to tick on vibes, +// which is the exact AI-accuracy failure the RUNNER.md protocol exists to +// prevent. +// +// It does NOT judge whether an item is testable or its oracle sufficient — no +// static check can. It guarantees the *structure* a run can be trusted against. +// +// Usage: node scripts/check-platform-checklist.mjs (pnpm check:platform-checklist) + +import { readdirSync, readFileSync, existsSync } from 'node:fs'; +import { join, basename } from 'node:path'; + +const ROOT = new URL('..', import.meta.url).pathname; +const AREAS_DIR = join(ROOT, 'docs/qa/platform-checklist/areas'); + +const STATUSES = new Set(['active', 'draft', 'retired']); +const PRIORITIES = new Set(['P0', 'P1', 'P2']); +const SURFACES = new Set(['browser', 'api', 'cli', 'build', 'mixed']); +const ORACLES = new Set(['api', 'network', 'screenshot', 'dom', 'log', 'test', 'build']); +const BLOCKED_BY = new Set(['fixture', 'environment', 'dependency', 'product-bug']); + +const errors = []; +const err = (file, id, msg) => errors.push(`${file}${id ? ` · ${id}` : ''}: ${msg}`); + +if (!existsSync(AREAS_DIR)) { + console.error(`check-platform-checklist: missing ${AREAS_DIR}`); + process.exit(1); +} + +const files = readdirSync(AREAS_DIR).filter((f) => f.endsWith('.json')).sort(); +if (files.length === 0) { + console.error('check-platform-checklist: no area files found — the ledger cannot be empty.'); + process.exit(1); +} + +const allIds = new Map(); // id -> file +const allItems = []; + +for (const file of files) { + let doc; + try { + doc = JSON.parse(readFileSync(join(AREAS_DIR, file), 'utf8')); + } catch (e) { + err(file, null, `does not parse as JSON: ${e.message}`); + continue; + } + const stem = basename(file, '.json'); + if (doc.area !== stem) err(file, null, `"area" is ${JSON.stringify(doc.area)} but the filename says "${stem}"`); + if (typeof doc.title !== 'string' || !doc.title) err(file, null, 'missing "title"'); + if (!Array.isArray(doc.items) || doc.items.length === 0) { + err(file, null, '"items" must be a non-empty array'); + continue; + } + + for (const item of doc.items) { + const id = typeof item.id === 'string' ? item.id : ''; + const where = (msg) => err(file, id, msg); + + if (!/^[a-z0-9-]+\.[a-z0-9-]+$/.test(id)) where('id must be "." in kebab-case'); + else if (!id.startsWith(`${doc.area}.`)) where(`id must be prefixed with its own area ("${doc.area}.")`); + if (allIds.has(id)) where(`duplicate id — already defined in ${allIds.get(id)}; ids are immutable and never reused`); + allIds.set(id, file); + allItems.push({ file, item }); + + if (typeof item.title !== 'string' || !item.title) where('missing "title"'); + if (!STATUSES.has(item.status)) where(`"status" must be one of ${[...STATUSES].join('|')}`); + if (!PRIORITIES.has(item.priority)) where(`"priority" must be one of ${[...PRIORITIES].join('|')}`); + if (!SURFACES.has(item.surface)) where(`"surface" must be one of ${[...SURFACES].join('|')}`); + if (typeof item.since !== 'string' || !/^v\d+(\.\d+)?$/.test(item.since)) { + where('"since" must be the release that introduced the capability, e.g. "v16" or "v16.0"'); + } + + if (!Number.isInteger(item.revision) || item.revision < 1) where('"revision" must be an integer >= 1'); + if (!Array.isArray(item.history) || item.history.length === 0) { + where('"history" must be a non-empty array — every item records why it exists'); + } else { + const last = item.history[item.history.length - 1]; + if (last.revision !== item.revision) { + where(`"revision" (${item.revision}) must equal the last history entry's revision (${last.revision}) — a semantic edit bumps both`); + } + for (const h of item.history) { + if (!Number.isInteger(h.revision) || typeof h.date !== 'string' || typeof h.change !== 'string') { + where('each history entry needs { revision, date, change }'); + break; + } + } + } + + if (!Array.isArray(item.steps) || item.steps.length === 0) where('"steps" must be a non-empty array of strings'); + + if (item.status === 'retired') { + if (typeof item.retiredReason !== 'string' || !item.retiredReason) where('retired items must carry "retiredReason"'); + } else { + if (!Array.isArray(item.acceptance) || item.acceptance.length === 0) { + where('active/draft items must have at least one acceptance clause'); + } else { + item.acceptance.forEach((c, i) => { + if (typeof c.clause !== 'string' || !c.clause) where(`acceptance[${i}] missing "clause"`); + if (!ORACLES.has(c.oracle)) where(`acceptance[${i}] "oracle" must be one of ${[...ORACLES].join('|')}`); + if (typeof c.verify !== 'string' || !c.verify) where(`acceptance[${i}] missing "verify" — how the oracle is consulted`); + }); + } + } + + if (item.blocked !== undefined) { + if (!BLOCKED_BY.has(item.blocked?.by)) where(`"blocked.by" must be one of ${[...BLOCKED_BY].join('|')}`); + if (typeof item.blocked?.ref !== 'string' || !item.blocked.ref) { + where('"blocked.ref" must name the tracking issue/fixture gap — waive-with-a-reference, never silently'); + } + } + + if (item.automated !== undefined && item.automated !== null) { + if (typeof item.automated.ref !== 'string' || !item.automated.ref) where('"automated.ref" must point at the pinning test'); + } + } +} + +// Cross-file referential integrity: supersededBy must land on a real id. +for (const { file, item } of allItems) { + if (item.supersededBy !== undefined && !allIds.has(item.supersededBy)) { + err(file, item.id, `"supersededBy" points at unknown id "${item.supersededBy}"`); + } +} + +if (errors.length) { + console.error(`check-platform-checklist: ${errors.length} problem(s)\n`); + for (const e of errors) console.error(` ✗ ${e}`); + console.error('\nContract: docs/qa/platform-checklist/README.md (authoring) · RUNNER.md (execution).'); + process.exit(1); +} + +const total = allItems.length; +const active = allItems.filter(({ item }) => item.status === 'active').length; +console.log(`check-platform-checklist: OK — ${files.length} areas, ${total} items (${active} active).`); From f09f69033177612c86992dbbd3d3e44f2b15e097 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 13:24:01 +0000 Subject: [PATCH 02/14] =?UTF-8?q?feat(qa):=20=E8=A1=A5=E9=BD=90=E6=B8=85?= =?UTF-8?q?=E5=8D=95=E7=BC=BA=E5=A4=B1=E8=83=BD=E5=8A=9B=E5=9F=9F=20?= =?UTF-8?q?=E2=80=94=20ai=20=E4=B8=8E=20integration-system=20(11=20?= =?UTF-8?q?=E5=9F=9F=2046=20=E9=A1=B9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the two coverage holes the seed import left: nothing covered the AI metadata kinds (agent/tool/skill, MCP surfaces) or the integration/system services (declarative connectors, webhooks, jobs, email templates). - areas/ai.json — agent/tool/skill metadata round-trip (variants matrix), MCP HTTP transport both-sides (enabled 501/off + /mcp/skill public), stdio fail-closed + RLS/FLS parity (from #3358 §9), run_action ai.exposed gate + audit (15.1 §A9), validate_expression. Showcase ships no AI seeds (ADR-0063) — fixture requirements declared explicitly. - areas/integration-system.json — declarative connector lifecycle from the 15.1 §B rows (#2994/#3062 boot materialization, #3049 degraded husk + atomic recovery, #3059 stdio default-deny allowlist, #3024 spec-path escape rejection, #2985 descriptor-only boot audit, objectui#2563 designer picker), webhook live-fire + retired-trigger build gate, job scheduled run, email-template variable rendering. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01YD9f6FYyMraUWYeJf53V43 --- docs/qa/platform-checklist/areas/ai.json | 187 ++++++++++++ .../areas/integration-system.json | 283 ++++++++++++++++++ 2 files changed, 470 insertions(+) create mode 100644 docs/qa/platform-checklist/areas/ai.json create mode 100644 docs/qa/platform-checklist/areas/integration-system.json diff --git a/docs/qa/platform-checklist/areas/ai.json b/docs/qa/platform-checklist/areas/ai.json new file mode 100644 index 0000000000..d0f02cd255 --- /dev/null +++ b/docs/qa/platform-checklist/areas/ai.json @@ -0,0 +1,187 @@ +{ + "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md. NOTE: AI is deliberately absent from the showcase app (ADR-0063 — platform-owned), so most items here carry explicit fixture requirements instead of assuming showcase seeds.", + "area": "ai", + "title": "AI — agents, tools, skills, MCP", + "items": [ + { + "id": "ai.agent-tool-skill-metadata-roundtrip", + "title": "agent / tool / skill metadata kinds author, persist and list over the meta + REST surfaces", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": ["admin"], + "fixtures": { + "app": "any", + "requires": ["a writable package to author into (AI artifacts are not seeded by the showcase — ADR-0063)"], + "knownGaps": ["no stock example app ships AI seeds; a minimal agent+tool+skill fixture config is needed for the full round-trip"] + }, + "variants": ["agent", "tool", "skill"], + "steps": [ + "author one metadata item per variant (defineAgent / defineTool / defineSkill shapes from @objectstack/spec src/ai/*.zod.ts) in a writable package", + "build/boot the app; capture the boot log for AI registration lines", + "GET /api/v1/meta/types/agent (and tool, skill) to confirm the kinds are served by the registry", + "list the authored items over the REST surface (routes under /api/v1/ai/** — e.g. GET /api/v1/ai/agents, the REST-plural example of AGENTS.md PD#3)", + "re-read one item and field-diff it against the authored source" + ], + "acceptance": [ + { + "clause": "each of the three kinds parses against its Zod schema at build — an off-spec shape fails at authoring, not silently at runtime", + "oracle": "build", + "verify": "a deliberately broken variant (unknown key) is rejected with a located error; the valid ones build clean", + "evidence": "build output for both cases" + }, + { + "clause": "the metadata registry serves all three kinds (they are registered metadata types, singular names per PD#3)", + "oracle": "api", + "verify": "GET /api/v1/meta/types/ returns the kind's schema/registration for agent, tool, skill", + "evidence": "the three responses" + }, + { + "clause": "authored items are listed and readable over /api/v1/ai/** and survive a field-diff against source", + "oracle": "api", + "verify": "list + get round-trip; every authored field present with the authored value", + "evidence": "list/get responses + diff" + } + ], + "negative": [ + "an unauthenticated request to the /api/v1/ai/** list route → 401 (anonymous-deny holds on the AI family too)" + ], + "traps": ["stale-dist"], + "source": ["packages/spec/src/ai/agent.zod.ts (+tool/skill)", "packages/runtime/src/route-ledger.ts ('* /ai/**')", "packages/spec/liveness/agent.json"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — closes the AI hole in capability coverage (no area covered agent/tool/skill kinds)", "ref": "claude/platform-test-checklist-ocwugl" }] + }, + { + "id": "ai.mcp-http-surface", + "title": "MCP HTTP transport and the public /mcp/skill endpoint behave per config", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "fixtures": { + "app": "any", + "requires": ["one boot with MCP HTTP enabled and one with it disabled (the #3358 sweep hit the disabled config and read 501)"] + }, + "steps": [ + "boot with MCP HTTP disabled; GET /mcp and /mcp/skill; record statuses", + "boot with MCP HTTP enabled; GET /mcp/skill unauthenticated", + "open an MCP Streamable HTTP session against /mcp and list tools" + ], + "acceptance": [ + { + "clause": "disabled config answers a deliberate 501/refusal on /mcp — a clean 'off', not a hang or 500", + "oracle": "api", + "verify": "status + body of GET /mcp on the disabled boot", + "evidence": "the response" + }, + { + "clause": "GET /mcp/skill serves the public SKILL.md for agents without auth (documented server-only surface)", + "oracle": "api", + "verify": "200 + markdown body on the enabled boot, unauthenticated", + "evidence": "the response head" + }, + { + "clause": "an MCP client can complete the handshake and list tools on the enabled boot", + "oracle": "network", + "verify": "initialize + tools/list round-trip succeeds over Streamable HTTP", + "evidence": "the session trace" + } + ], + "negative": ["the enabled boot must still reject tool CALLS that need auth when the session carries none — transport up ≠ authz open"], + "traps": ["dispatcher-vs-hono-route"], + "source": ["packages/runtime/src/route-ledger.ts ('* /mcp/**', 'GET /mcp/skill')", "#3358 §9 (MCP HTTP 501 on the swept config)"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — the #3358 sweep could not drive MCP because the config was off; both sides are now explicit", "ref": "claude/platform-test-checklist-ocwugl" }] + }, + { + "id": "ai.mcp-stdio-fail-closed", + "title": "MCP stdio auto-start fails closed without an API key; with a key, reads honor RLS/FLS", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P0", + "surface": "cli", + "personas": ["restricted member API key (for the RLS half)"], + "steps": [ + "attempt MCP stdio auto-start WITHOUT OS_MCP_STDIO_API_KEY set; capture the refusal", + "start it with a valid key bound to a restricted member; over MCP, read an OWD-private object", + "read the same object as an admin-keyed session; compare row sets" + ], + "acceptance": [ + { + "clause": "stdio auto-start without the key refuses to serve (fail-closed), with a clear message naming the missing key", + "oracle": "log", + "verify": "the refusal line in the process output", + "evidence": "the log line" + }, + { + "clause": "with a member-bound key, MCP reads return only RLS-visible rows and FLS-masked fields — identical to the same member over REST", + "oracle": "api", + "verify": "row-set + field-set comparison MCP-vs-REST for the same member; admin session shows the full set (both sides)", + "evidence": "the compared reads" + } + ], + "negative": ["an invalid/revoked key must be rejected, not fall back to an anonymous-but-serving session"], + "traps": ["wrong-persona"], + "source": ["#3358 §9 (verified PASS: fail-closed guard present, reads honor RLS/FLS)"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358 §9", "ref": "#3358" }] + }, + { + "id": "ai.mcp-run-action-exposure-gate", + "title": "MCP run_action requires ai.exposed and audits the call", + "since": "v15.1", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "fixtures": { "app": "any", "requires": ["one action declared ai.exposed and one without, on the same object"] }, + "steps": [ + "over MCP, call run_action on the undeclared action; capture the refusal", + "call the ai.exposed action; capture result", + "read the audit log for the successful call" + ], + "acceptance": [ + { + "clause": "the undeclared action is rejected fail-closed (declared ≠ exposed is the gate, PD#10 discipline)", + "oracle": "api", + "verify": "the MCP call errors with the exposure refusal; the action did NOT execute (verify by its side effect being absent)", + "evidence": "the refusal + absence check" + }, + { + "clause": "the exposed action executes and lands in the audit log with the caller identity", + "oracle": "api", + "verify": "action side effect present + audit row for the call", + "evidence": "result + audit row" + } + ], + "negative": ["the refusal path must leave zero side effects — a rejected call that half-executed is a FAIL"], + "source": ["docs/plans/release-15.1-test-plan.md §A9 (#2964)"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the 15.1 plan §A9", "ref": "claude/platform-test-checklist-ocwugl" }] + }, + { + "id": "ai.mcp-validate-expression", + "title": "MCP validate_expression returns errors/warnings/inferred type against an object schema", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "api", + "fixtures": { "app": "any", "requires": ["MCP HTTP enabled (see ai.mcp-http-surface)"] }, + "steps": [ + "over MCP, validate a correct formula against a seeded object schema", + "validate a formula with a type error (e.g. date arithmetic) and one referencing a nonexistent field" + ], + "acceptance": [ + { + "clause": "the valid expression returns ok with an inferred type; the invalid ones return located errors naming the problem (bad arithmetic, unknown field)", + "oracle": "api", + "verify": "three calls, three verdicts matching the formula gates the build enforces (api-backend.formula-gates)", + "evidence": "the three responses" + } + ], + "source": ["#3358 §9 (underlying validateExpression works; MCP path was blocked on the disabled transport)"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358 §9", "ref": "#3358" }] + } + ] +} diff --git a/docs/qa/platform-checklist/areas/integration-system.json b/docs/qa/platform-checklist/areas/integration-system.json new file mode 100644 index 0000000000..72136ee4d7 --- /dev/null +++ b/docs/qa/platform-checklist/areas/integration-system.json @@ -0,0 +1,283 @@ +{ + "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md.", + "area": "integration-system", + "title": "Integration & system services — connectors, webhooks, jobs, email templates", + "items": [ + { + "id": "integration-system.connector-declarative-boot", + "title": "A declarative provider-bound connector materializes at boot", + "since": "v15.1", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "fixtures": { "app": "showcase", "requires": ["the live provider:'mcp' demo connector instance shipped with the showcase (#2994/#3062)"] }, + "steps": [ + "boot the showcase; read the boot log for connector materialization", + "GET /api/v1/automation/connectors (route ledger: automation.listConnectors)", + "locate the declarative instance in the listing and read its descriptor" + ], + "acceptance": [ + { + "clause": "the declarative instance appears in the connector listing with origin:'declarative' and a healthy state", + "oracle": "api", + "verify": "GET /automation/connectors contains the instance with origin:'declarative'", + "evidence": "the listing" + }, + { + "clause": "its declared actions are enumerable from the descriptor (the instance is usable, not just listed)", + "oracle": "api", + "verify": "the descriptor carries the action set the declaration authored", + "evidence": "the descriptor read" + } + ], + "negative": ["deleting/disabling the declaration and rebooting must remove/disable the instance — materialization tracks the metadata, it does not fossilize"], + "traps": ["stale-dist"], + "source": ["docs/plans/release-15.1-test-plan.md §B1 (#2994/#3062)", "packages/runtime/src/route-ledger.ts (GET /automation/connectors)", "packages/spec/src/integration/connector.zod.ts"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the 15.1 plan §B — connectors had no checklist coverage", "ref": "claude/platform-test-checklist-ocwugl" }] + }, + { + "id": "integration-system.connector-degraded-recovery", + "title": "Unreachable connector upstream degrades gracefully and recovers atomically", + "since": "v15.1", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "steps": [ + "point a declarative connector at an upstream, boot, confirm healthy", + "kill the upstream; restart the app server", + "read boot outcome + the instance descriptor; wait through a backoff cycle", + "restore the upstream; confirm recovery without another restart" + ], + "acceptance": [ + { + "clause": "boot does NOT hang or crash on the dead upstream — the instance lands as a degraded husk (descriptor state:'degraded')", + "oracle": "api", + "verify": "boot completes; GET /automation/connectors shows state:'degraded' for the instance", + "evidence": "boot log + descriptor" + }, + { + "clause": "retries back off (no hot loop) and recovery swaps the live instance in atomically", + "oracle": "log", + "verify": "retry lines show increasing intervals; after upstream restore the descriptor flips healthy without a server restart", + "evidence": "log excerpt + post-recovery descriptor" + } + ], + "negative": ["while degraded, invoking one of its actions must fail with a clear connector-unavailable error — not a timeout hang"], + "source": ["docs/plans/release-15.1-test-plan.md §B3 (#3049)"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the 15.1 plan §B3", "ref": "claude/platform-test-checklist-ocwugl" }] + }, + { + "id": "integration-system.connector-stdio-default-deny", + "title": "Declarative stdio connectors are denied unless the host allowlists them", + "since": "v15.1", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "cli", + "steps": [ + "author a declarative stdio connector WITHOUT host allowlist opt-in; boot", + "add the allowlist opt-in; boot again" + ], + "acceptance": [ + { + "clause": "the non-opted-in stdio instance is refused (boot fatal or reload skip, per the documented behavior) with a message naming the allowlist", + "oracle": "log", + "verify": "boot output for the deny case", + "evidence": "the refusal line" + }, + { + "clause": "the allowlisted boot materializes the instance normally", + "oracle": "api", + "verify": "GET /automation/connectors lists it healthy", + "evidence": "the listing" + } + ], + "negative": ["the deny must be default — absence of any allowlist config means refused, never silently spawned processes"], + "source": ["docs/plans/release-15.1-test-plan.md §B4 (#3059)"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the 15.1 plan §B4", "ref": "claude/platform-test-checklist-ocwugl" }] + }, + { + "id": "integration-system.connector-spec-path-no-escape", + "title": "Connector openapi spec paths resolve package-relative and reject escapes", + "since": "v15.1", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "build", + "steps": [ + "author providerConfig.spec: './openapi.json' inside the package; build/boot", + "author providerConfig.spec: '../outside.json'; build/boot" + ], + "acceptance": [ + { + "clause": "the package-relative path resolves and the connector's actions derive from the spec", + "oracle": "api", + "verify": "descriptor carries the spec-derived actions", + "evidence": "descriptor read" + }, + { + "clause": "the '../' escape is rejected with a located error — path traversal out of the package is not a supported shape", + "oracle": "build", + "verify": "build/boot refuses the escaping path", + "evidence": "the error" + } + ], + "source": ["docs/plans/release-15.1-test-plan.md §B5 (#3024)"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the 15.1 plan §B5", "ref": "claude/platform-test-checklist-ocwugl" }] + }, + { + "id": "integration-system.connector-descriptor-audit", + "title": "Descriptor-only connector contracts are audited at boot", + "since": "v15.1", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "cli", + "steps": [ + "declare connector actions that have no runtime registration; boot and read the audit", + "set enabled:false on the declaration; boot again" + ], + "acceptance": [ + { + "clause": "actions without runtime registration produce a boot warning naming the gap (declared ≠ delivered surfaces loudly, PD#10)", + "oracle": "log", + "verify": "the warning line names the connector + missing actions", + "evidence": "log excerpt" + }, + { + "clause": "enabled:false silences the audit for that declaration — the opt-out is deliberate and quiet", + "oracle": "log", + "verify": "no warning on the disabled boot", + "evidence": "log excerpt" + } + ], + "source": ["docs/plans/release-15.1-test-plan.md §B6 (#2985)"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the 15.1 plan §B6", "ref": "claude/platform-test-checklist-ocwugl" }] + }, + { + "id": "integration-system.flow-connector-picker", + "title": "The flow designer's connector picker lists instances and marks declarative ones", + "since": "v15.1", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "personas": ["admin"], + "steps": [ + "in the flow designer, add a connector_action node", + "open the connector picker; screenshot; compare with GET /automation/connectors" + ], + "acceptance": [ + { + "clause": "the picker lists the same instances the API reports, with declarative instances annotated", + "oracle": "screenshot", + "verify": "picker screenshot cross-checked against the connector listing", + "evidence": "screenshot + API read" + } + ], + "traps": ["stale-console-bundle", "hydration-race"], + "source": ["docs/plans/release-15.1-test-plan.md §B2 (objectui#2563)"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the 15.1 plan §B2", "ref": "claude/platform-test-checklist-ocwugl" }] + }, + { + "id": "integration-system.webhook-lifecycle", + "title": "Webhooks: live trigger events register and fire; retired trigger kinds are rejected", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "fixtures": { "app": "any", "requires": ["a reachable webhook receiver (local echo server on the run's own port range)"] }, + "steps": [ + "author a webhook on a live record event (per the trigger set packages/spec/liveness/webhook.json documents as live); boot", + "mutate a matching record; capture the delivery at the receiver", + "author a webhook using a RETIRED trigger kind (the enforce-or-remove sweep removed undelete/api); build" + ], + "acceptance": [ + { + "clause": "the live-event webhook delivers to the receiver with the event payload on the matching mutation", + "oracle": "network", + "verify": "receiver logs the POST with the record event", + "evidence": "the received request" + }, + { + "clause": "a retired trigger kind fails at parse/build with located guidance — never registers silently dead", + "oracle": "build", + "verify": "authoring the retired kind errors (same gate the #3358 enforce-or-remove sweep verified)", + "evidence": "the build error" + } + ], + "negative": ["an unreachable receiver must surface delivery failure observably (log/status), not drop silently"], + "source": ["packages/spec/liveness/webhook.json", "#3358 §9 (webhook undelete/api trigger removal gate)"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — webhook kind had no checklist coverage", "ref": "claude/platform-test-checklist-ocwugl" }] + }, + { + "id": "integration-system.job-scheduled-run", + "title": "A job metadata item schedules, executes, and records its run", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "fixtures": { "app": "any", "requires": ["a job authored with a near-term schedule (or a manually-runnable job) — packages/spec/src/system/job.zod.ts is the shape"] }, + "steps": [ + "author a job per the spec shape in a writable package; boot", + "confirm registration (meta read / boot log), then let it fire or trigger it manually", + "read the run evidence (log lines / run records) and the job's side effect" + ], + "acceptance": [ + { + "clause": "the job registers at boot and its execution is observable (run record or timestamped log) with the side effect present", + "oracle": "log", + "verify": "boot registration line + execution line + the side effect verified over the data API", + "evidence": "log excerpts + API read" + }, + { + "clause": "a failing job surfaces an ERROR with the failure cause — not a silent skip", + "oracle": "log", + "verify": "a deliberately-throwing job body produces the ERROR line", + "evidence": "log excerpt" + } + ], + "source": ["packages/spec/src/system/job.zod.ts", "packages/spec/liveness/job.json"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — job kind had no checklist coverage", "ref": "claude/platform-test-checklist-ocwugl" }] + }, + { + "id": "integration-system.email-template-render", + "title": "Email templates resolve variables and dispatch through the configured channel", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "fixtures": { + "app": "any", + "requires": ["an email_template authored per packages/spec/src/system/email-template.zod.ts + a capturable email channel (dev transport / log capture — no real SMTP needed)"], + "knownGaps": ["no stock example exercises an email template end-to-end; needs a small fixture flow with a notify/email step"] + }, + "steps": [ + "author an email_template with variables; reference it from a flow notify/email step", + "run the flow with known variable values", + "capture the rendered output at the dev transport / log" + ], + "acceptance": [ + { + "clause": "the rendered subject/body substitute every variable with the run's values — no {{placeholder}} residue", + "oracle": "log", + "verify": "captured output contains the substituted values and zero unresolved placeholders", + "evidence": "the captured render" + }, + { + "clause": "a template referencing an undefined variable surfaces a located authoring/run error, not a silently-empty substitution", + "oracle": "log", + "verify": "the undefined-variable case errors visibly", + "evidence": "the error" + } + ], + "source": ["packages/spec/src/system/email-template.zod.ts", "packages/spec/liveness/email_template.json"], + "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — email_template kind had no checklist coverage", "ref": "claude/platform-test-checklist-ocwugl" }] + } + ] +} From 2ce429aa28ba2bd75ed17ba89e7edbfce284244f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 13:30:53 +0000 Subject: [PATCH 03/14] =?UTF-8?q?feat(qa):=20=E6=B7=B1=E5=8C=96=20dashboar?= =?UTF-8?q?ds=20=E4=B8=8E=20i18n=20=E5=9F=9F=E5=88=B0=E6=B7=B1=E6=B5=8B?= =?UTF-8?q?=E5=A5=91=E7=BA=A6=20(rev=202)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First two areas rewritten to the deep-test contract: concrete draft-door/API steps, clause-grained acceptance citing the exact spec sources (dashboard.zod.ts LEGACY key list as a variants matrix, chart.zod.ts wrong-layer guidance, translation.zod.ts metadataForms group), rejected-draft-not-persisted checks, per-recipient locale controls, and round-trip/symmetry clauses. Existing items bumped to revision 2 with history entries; new items added per area. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01YD9f6FYyMraUWYeJf53V43 --- .../platform-checklist/areas/dashboards.json | 572 +++++++++++++++++- docs/qa/platform-checklist/areas/i18n.json | 375 +++++++++++- 2 files changed, 902 insertions(+), 45 deletions(-) diff --git a/docs/qa/platform-checklist/areas/dashboards.json b/docs/qa/platform-checklist/areas/dashboards.json index 14e936eee0..df8bf781fd 100644 --- a/docs/qa/platform-checklist/areas/dashboards.json +++ b/docs/qa/platform-checklist/areas/dashboards.json @@ -8,77 +8,597 @@ "title": "A widget authored with a stray/legacy key fails loud, never renders blank", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "mixed", "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "a WRITABLE package for dashboard drafts — the metadata draft door (PUT /api/v1/meta/dashboard/?mode=draft) accepts a scratch dashboard name, so no showcase file needs touching; if the deployment blocks runtime metadata writes, record that as a fixture requirement" + ] + }, "steps": [ - "author a dashboard widget carrying a legacy key (e.g. categoryField) or a hallucinated key", - "load the dashboard / run the parse" + "boot the showcase app, sign in as admin, and pick a scratch dashboard name (e.g. qa_stray_key_probe) so no shipped dashboard is mutated", + "for each variant in `variants`, author a dashboard draft via PUT /api/v1/meta/dashboard/qa_stray_key_probe?mode=draft whose single widget carries a valid dataset binding (dataset: showcase_task_metrics, dimensions: ['status'], values: ['task_count']) PLUS the stray key (e.g. \"categoryField\": \"status\")", + "capture the full response status and error body for each variant", + "additionally author one widget with a wrong-layer key INSIDE chartConfig (e.g. chartConfig.dataset or chartConfig.aggregate) — ChartConfigSchema carries per-key guidance for these — and capture that error text too", + "attempt the same stray-key widget through the Studio dashboard designer inspector (browser door) and capture the surfaced error", + "after each rejection, GET /api/v1/meta/dashboard/qa_stray_key_probe and record whether anything was persisted", + "finally author the SAME widget with the stray key removed (pure dataset + dimensions + values) and load it in the browser" ], "acceptance": [ { - "clause": "the parse rejects the widget with a loud error NAMING the offending key and pointing at the dataset shape — the old failure mode (silently renders nothing) must not reproduce", + "clause": "every legacy inline-analytics key variant is rejected with an error NAMING the offending key", + "oracle": "log", + "verify": "for each of the 11 LEGACY keys enumerated in packages/spec/src/ui/dashboard.zod.ts (the pre-ADR-0021 shape removed at @objectstack/spec 9.0.0), the error body contains the literal key name", + "evidence": "the per-variant error texts, keyed by variant" + }, + { + "clause": "the error points at the expected dataset+dimensions+values shape (ADR-0021), giving the author the fix — not just 'unrecognized key'", + "oracle": "log", + "verify": "error text cites binding a `dataset` and selecting `dimensions`/`values` (the DashboardWidgetSchema error map's prescription)", + "evidence": "error text excerpt" + }, + { + "clause": "a hallucinated key (one that never existed, e.g. `chartFlavour`) is also rejected loudly with the key echoed back and a did-you-mean suggestion where one is close", + "oracle": "log", + "verify": "error names the hallucinated key; the strictObject suggester output is present or absent honestly", + "evidence": "error text" + }, + { + "clause": "wrong-layer keys inside chartConfig (`dataset`, `aggregate`, `objectName`, `drillDown`) are rejected with the guidance naming the surface the key actually belongs to", "oracle": "log", - "verify": "the error names the key and cites the expected dataset+dimensions+values shape (ADR-0021)", - "evidence": "the error text" + "verify": "error for chartConfig.dataset says it is the widget's own key (ADR-0021 sibling of chartConfig); error for chartConfig.drillDown names the react-tier prop, per the guidance map in packages/spec/src/ui/chart.zod.ts", + "evidence": "the two error texts" + }, + { + "clause": "a rejected draft is NOT persisted — the rejection is authoritative, not cosmetic", + "oracle": "api", + "verify": "GET /api/v1/meta/dashboard/qa_stray_key_probe after each rejected PUT returns 404 or the last GOOD revision, never a body containing the stray key", + "evidence": "the GET responses paired with each rejected PUT" + }, + { + "clause": "a well-formed widget with the same data renders — the gate rejects the KEY, not the dataset", + "oracle": "screenshot", + "verify": "the corrected widget (dataset: showcase_task_metrics, dimensions: ['status'], values: ['task_count']) draws a real bar chart in the browser", + "evidence": "screenshot of the corrected widget" } ], - "negative": ["this item IS a negative test — its pass condition is the rejection; a blank-but-no-error render is a FAIL"], - "source": ["#3358 §3 (four stray-key variants all rejected with the named error)"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }] + "negative": [ + "the old failure mode — silently rendering nothing — must not reproduce for ANY variant; blank-without-error is a FAIL", + "a 2xx on a stray-key draft save is a FAIL even if the dashboard later renders: the strict gate exists at the parse, not the paint" + ], + "variants": [ + "object", + "categoryField", + "categoryGranularity", + "valueField", + "aggregate", + "aggregation", + "rowField", + "columnField", + "xAxisField", + "yAxisFields", + "measures", + "chartFlavour (hallucinated control)" + ], + "traps": ["stale-console-bundle"], + "source": [ + "#3358 §3 (four stray-key variants all rejected with the named error)", + "packages/spec/src/ui/dashboard.zod.ts (LEGACY key list + strict error map, ADR-0021 single-form cutover)", + "packages/spec/src/ui/chart.zod.ts (ChartConfigSchema wrong-layer guidance: dataset/aggregate/objectName/drillDown)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "dashboards.chart-first-paint", "title": "Charts draw on first paint — no blank-until-resize", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", "surface": "browser", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "stock showcase seed (tasks span every status/priority and multiple created_at months — src/data/seed/index.ts sizes the seed to feed every view)" + ] + }, "steps": [ - "cold-load a dashboard with bar charts", - "screenshot immediately after render settles, WITHOUT resizing the window" + "before touching the browser, establish ground truth: POST the dataset query (or GET /api/v1/data/showcase_task with a status aggregate) and record how many distinct status buckets the seed actually produces", + "cold-load the Chart Gallery dashboard (Showcase app → Analytics → Chart Gallery, dashboard name showcase_chart_gallery) in a fresh tab at a fixed window size", + "screenshot immediately after render settles, WITHOUT resizing the window", + "repeat the cold-load + immediate screenshot on the Delivery Operations dashboard (showcase_ops_dashboard) — its KPI hero row plus comparison/trend charts is the other first-paint composition", + "only after the screenshot confirms rendering, read the DOM to count the recharts SVG bars in Tasks by Status", + "capture the browser console log for the whole load" ], "acceptance": [ { - "clause": "bars/axes are drawn on the first paint", + "clause": "bars/axes are drawn on the first paint, with no window resize", "oracle": "screenshot", - "verify": "the first-paint screenshot shows a real chart (recharts SVG with bars), not an empty plot area", - "evidence": "screenshot" + "verify": "the first-paint screenshot shows a real chart (recharts SVG with bars and axes), not an empty plot area", + "evidence": "first-paint screenshots of both dashboards" + }, + { + "clause": "the drawn chart reflects the multi-bucket seed, not one lonely datapoint — the tick must say what it looks like it says", + "oracle": "api", + "verify": "the bar count in Tasks by Status equals the distinct-status count established from the data API before the browser was opened", + "evidence": "the API bucket count next to the DOM bar count" + }, + { + "clause": "KPI metric tiles on Delivery Operations render numeric values on first paint (not placeholder dashes that never resolve)", + "oracle": "screenshot", + "verify": "the hero-row tiles show numbers consistent with a direct dataset query for the same measures", + "evidence": "screenshot + the comparison query result" + }, + { + "clause": "no chart error and no silent failure: the console log carries no dataset-query error during first paint, and every widget either draws or shows a named error state", + "oracle": "log", + "verify": "browser console capture for the load contains no swallowed widget/query failure; any failed widget shows a visible error, not an empty plot", + "evidence": "console log excerpt" } ], + "negative": [ + "a chart area that stays blank until the window is resized is the FAIL this item exists for, even if it draws afterwards" + ], "traps": ["single-datapoint", "hydration-race"], - "source": ["#3358 §3 — note its caveat: with thin seeds every widget draws one data point, so the tick 'says less than it looks'; prefer multi-bucket fixtures"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358 with the thin-seed caveat attached", "ref": "#3358" }] + "source": [ + "#3358 §3 — note its caveat: with thin seeds every widget draws one data point, so the tick 'says less than it looks'; prefer multi-bucket fixtures", + "examples/app-showcase/src/ui/dashboards/chart-gallery.dashboard.ts", + "examples/app-showcase/src/ui/dashboards/ops-dashboard.dashboard.ts" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358 with the thin-seed caveat attached", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "dashboards.drill-through-range", "title": "Report drill-through scopes the drilled list to the exact bucket range", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", "surface": "browser", + "personas": ["admin"], "fixtures": { "app": "showcase", - "requires": ["a date-bucketed report with MULTIPLE buckets to drill (blocked historically by #3415 — seed validation silently rejected 4 of 5 projects, leaving single-point charts)"] + "requires": [ + "a date-bucketed report with MULTIPLE buckets to drill (blocked historically by #3415 — seed validation silently rejected 4 of 5 projects, leaving single-point charts)", + "the seeded showcase reports with drilldown: true — showcase_hours_by_status (summary), showcase_status_priority_matrix (matrix) — and the Chart Gallery line_created widget (month-bucketed created_at) for the date-bucket case" + ] }, "steps": [ - "drill a date-bucket cell (e.g. a quarter)", - "capture the drilled list's query" + "establish ground truth first: query showcase_task via /api/v1/data with a status aggregate and a created_at month aggregate, recording each bucket's count", + "open the report showcase_hours_by_status (nav 'Hours by Status'), click a status bucket cell, and capture the drilled list's network request", + "open the Chart Gallery dashboard and click a month bucket on the line_created widget (dataset-bound widgets drill through the semantic layer — the drill target and filter derive from the clicked dataset row, per the ADR-0021 note in dashboard.zod.ts)", + "capture the drill request for the month click, including its date bounds", + "count the rows in each drilled list (page through if paginated) and compare to the bucket's aggregate", + "click a cell in showcase_status_priority_matrix (status × priority) and verify the drilled list is scoped by BOTH dimensions", + "screenshot the drill drawer each time (drill opens in-place as a drawer by default — chart.zod.ts drill target enum drawer|dialog|navigate)" ], "acceptance": [ { - "clause": "the drilled list is scoped to exactly that time range — not a superset", + "clause": "the drilled list for a date bucket is scoped to exactly that time range — not a superset", + "oracle": "network", + "verify": "the drill request carries the bucket's from/to bounds (a month click carries that month's bounds, not the year's)", + "evidence": "the captured drill query" + }, + { + "clause": "the drilled row count equals the clicked bucket's aggregate", + "oracle": "api", + "verify": "count of drilled rows == the bucket count from the pre-established aggregate query, for both the status drill and the month drill", + "evidence": "count comparison table" + }, + { + "clause": "a matrix cell drill is scoped by BOTH the row and column dimensions", "oracle": "network", - "verify": "the drill request carries the bucket's from/to bounds and the row count matches the bucket's aggregate", - "evidence": "the captured query + count comparison" + "verify": "the drill request from a status × priority cell carries both filters; row count matches that cell's value", + "evidence": "the captured query + count" + }, + { + "clause": "the drill is a strict subset when other buckets are non-empty — proving the scope is real, not cosmetic", + "oracle": "api", + "verify": "drilled count < total row count whenever the pre-established aggregate shows more than one non-empty bucket", + "evidence": "the two counts" + }, + { + "clause": "the drilled list opens in the drawer (default drill target) with the clicked category as its heading context", + "oracle": "screenshot", + "verify": "drawer screenshot shows the drilled records and a heading matching the clicked bucket's label", + "evidence": "drawer screenshot" } ], + "negative": [ + "a drill that opens the object's FULL unfiltered list while looking scoped (heading says the bucket, rows say everything) is a FAIL — the network trace is the authority, not the heading" + ], "traps": ["seed-data-thin", "single-datapoint"], - "source": ["#3358 §3", "#3415"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; runnable once multi-bucket seeds exist", "ref": "#3358" }] + "source": [ + "#3358 §3", + "#3415", + "packages/spec/src/ui/chart.zod.ts (drill semantics: derived filter from clicked category, target enum, drawer default)", + "packages/spec/src/ui/dashboard.zod.ts (ADR-0021 dataset-bound drill derives target+filter from the dataset row)", + "examples/app-showcase/src/ui/reports/index.ts (drilldown: true on all four reports)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; runnable once multi-bucket seeds exist", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "dashboards.chart-type-matrix", + "title": "Every ChartTypeSchema member renders a real chart with the correct marks and series count", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "the Chart Gallery dashboard (showcase_chart_gallery) — one widget per DISTINCTLY-rendered family, bound to showcase_task_metrics / showcase_project_metrics (ADR-0021)", + "a scratch draft dashboard (via PUT /api/v1/meta/dashboard/qa_single_value_gallery?mode=draft) for the four single-value synonym types the gallery deliberately does not duplicate" + ], + "knownGaps": [ + "kpi / gauge / solid-gauge / bullet have NO distinct renderer today — they render the same single value as `metric` (chart.zod.ts NOTE: 'honest single-value synonyms … gain a dial when a gauge renderer lands'; coverage.test.ts SAME_AS_METRIC). For these four the demonstrable claim is 'renders the value, not blank', NOT 'renders a dial' — a run must not tick a dial that does not exist" + ] + }, + "steps": [ + "read the enum first: packages/spec/src/ui/chart.zod.ts ChartTypeSchema currently has exactly 20 members (see variants) — if the enum has grown, this item's variants list is stale and must be revised before the run", + "establish ground truth from the data API: distinct status count, distinct priority count, and month-bucket count for showcase_task_metrics; account count for showcase_project_metrics", + "open the Chart Gallery dashboard (Showcase app → Analytics → Chart Gallery), let it settle, and screenshot the full board", + "per gallery widget (16 families: metric ×3 tiles, bar, column, horizontal-bar, line, area, combo, pie, donut, funnel, scatter, radar, treemap, sankey, table, pivot), screenshot the widget, then verify in the DOM that a real SVG (or table element for table/pivot) rendered with the mark type the family names", + "count marks against ground truth: bar/column/horizontal-bar bars == distinct statuses (or priorities), pie/donut/funnel segments == distinct statuses/priorities, line/area points == month buckets", + "for combo, verify BOTH mark types on shared axes: task_count as bars bound to the left axis and avg_progress as a line on the right axis (the widget's series[] declares exactly that)", + "for table and pivot, verify real grouped tables: Projects by Account rows == distinct accounts with 3 measure columns; Tasks by Status × Priority cross-tab dimensions match distinct status × priority", + "author the scratch draft dashboard with four widgets of types kpi, gauge, solid-gauge, bullet, each bound to dataset showcase_task_metrics, values ['task_count'], publish it, and load it", + "verify each of the four renders the same numeric value the metric tile shows — a number, never a blank tile", + "capture the per-variant verdict table" + ], + "acceptance": [ + { + "clause": "each of the 20 ChartTypeSchema variants renders a real chart (SVG marks or a real table), verified per-variant — no variant may be inferred from a sibling", + "oracle": "screenshot", + "verify": "one screenshot per variant; DOM mark-check only AFTER the screenshot confirms the surface rendered", + "evidence": "per-variant screenshot set + verdict table" + }, + { + "clause": "series/mark counts match the dataset's known values — a bar chart over status draws exactly as many bars as the seed has distinct statuses", + "oracle": "api", + "verify": "for bar, column, horizontal-bar, pie, donut, funnel, radar, line, area: mark/segment/point count equals the pre-established aggregate bucket count", + "evidence": "API bucket counts vs DOM mark counts, per widget" + }, + { + "clause": "combo renders MIXED marks on dual axes — bars (left) and a line (right) in one plot, per its series[].type / series[].yAxis config", + "oracle": "screenshot", + "verify": "the combo widget shows both rect-bars and a path-line, with two y-axes", + "evidence": "combo screenshot + DOM excerpt" + }, + { + "clause": "composition/relationship families draw their DISTINCTIVE geometry, not a fallback bar: treemap draws nested rectangles, sankey draws flow links, scatter draws points", + "oracle": "screenshot", + "verify": "each of treemap/sankey/scatter is visually its own family", + "evidence": "the three screenshots" + }, + { + "clause": "the four single-value synonyms (kpi, gauge, solid-gauge, bullet) render the SAME value as metric — a number, never a blank or an error", + "oracle": "screenshot", + "verify": "scratch-dashboard tiles each show the task_count value; evidence must note these are value-only renders (no dial) per the spec's own NOTE", + "evidence": "scratch-dashboard screenshot annotated with the honest-synonym caveat" + }, + { + "clause": "pivot renders a true cross-tab: status down × priority across with cell values reconciling to the API aggregate", + "oracle": "api", + "verify": "pivot row/column headers match distinct status/priority sets; spot-check 3 cells against the aggregate query", + "evidence": "pivot screenshot + 3-cell comparison" + }, + { + "clause": "demonstrability is pinned by the coverage ratchet — the enum cannot grow past the gallery silently", + "oracle": "test", + "verify": "run examples/app-showcase/test/coverage.test.ts ('covers every distinctly-renderable ChartType'); it enumerates ChartTypeSchema minus SAME_AS_METRIC against the gallery's widgets. Note: this pins declaration coverage only — it does NOT replace the browser render checks above", + "evidence": "the test output" + } + ], + "negative": [ + "any variant rendering as a DIFFERENT family than named (e.g. sankey falling back to a bar) is a FAIL — advertising a type that renders as something else is the exact failure the trimmed enum exists to prevent", + "a blank widget with no error for any variant is a FAIL" + ], + "variants": [ + "bar", + "horizontal-bar", + "column", + "line", + "area", + "pie", + "donut", + "funnel", + "scatter", + "treemap", + "sankey", + "combo", + "gauge", + "solid-gauge", + "metric", + "kpi", + "bullet", + "radar", + "table", + "pivot" + ], + "traps": ["single-datapoint", "hydration-race", "stale-console-bundle"], + "source": [ + "packages/spec/src/ui/chart.zod.ts (ChartTypeSchema — 20 members; NOTE on trimmed variants and single-value synonyms)", + "examples/app-showcase/src/ui/dashboards/chart-gallery.dashboard.ts", + "examples/app-showcase/src/ui/datasets/chart-gallery.dataset.ts", + "examples/app-showcase/src/coverage.ts + examples/app-showcase/test/coverage.test.ts (SAME_AS_METRIC waiver)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new matrix item: per-variant render proof for the full ChartTypeSchema enum, grounded in the Chart Gallery + coverage ratchet", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "dashboards.dataset-report-authoring", + "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": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "the four seeded reports (showcase_hours_by_status, showcase_status_priority_matrix, showcase_task_overview, showcase_hours_by_status_chart) and the semantic datasets they bind (showcase_task_metrics)" + ], + "knownGaps": [ + "ReportType 'tabular' is deliberately NOT demonstrated as a report — ADR-0021 Phase 2 converted the flat TaskListReport into the `tabular` ListView on showcase_task (an object-bound row lens, ADR-0017); the coverage test filters it out. Verify the variant via that ListView, do not demand a tabular report fixture" + ] + }, + "steps": [ + "author a designer-shaped dashboard draft via PUT /api/v1/meta/dashboard/qa_designer_probe?mode=draft: columns set, widgets binding dataset + dimensions/values BY NAME and carrying NO layout (exactly what the Studio designer's addWidget writes) — e.g. a metric on task_count, a bar on status, a donut on priority", + "publish the draft and load the published dashboard in the browser", + "read the published metadata back via GET /api/v1/meta/dashboard/qa_designer_probe and inspect the stored widget shape", + "open each seeded report from the Analytics nav: Hours by Status (summary), Status × Priority (matrix), Task Overview (joined), Hours by Status (Chart)", + "for the summary report, compare each row's est_hours sum to a direct dataset/aggregate query for that status", + "for the matrix report, verify the rows × columns cross-tab (status down, priority across) and reconcile 3 cells to the aggregate", + "for the joined report, verify BOTH blocks render (open_block with runtimeFilter done:false, done_block with done:true) and that their totals partition the task population", + "for the chart report, verify the embedded DatasetReportChart plots the same measure the table shows (bar over status × est_hours, a second queryDataset call per ADR-0021)", + "verify the tabular variant via the showcase_task `tabular` ListView ('Task List' nav node)", + "attempt the negative: author a joined report whose blocks[] contains a block of type 'joined' and capture the rejection" + ], + "acceptance": [ + { + "clause": "a layout-less designer-shaped draft saves (200) and publishes — the exact shape that used to 422 and lock Publish", + "oracle": "api", + "verify": "PUT ?mode=draft returns 200 and the publish call succeeds; the pinning dogfood test may serve as evidence per rule 6", + "evidence": "the two responses (or the pinned test's output)" + }, + { + "clause": "the published dashboard persists the ADR-0021 single form — dataset + dimensions + values, no legacy inline-analytics keys", + "oracle": "api", + "verify": "GET /api/v1/meta/dashboard/qa_designer_probe body: every widget has dataset+values, none has object/categoryField/valueField/aggregate", + "evidence": "the metadata read" + }, + { + "clause": "the published layout-less widgets RENDER (auto-flowed grid), proving publish produced a usable dashboard, not just a stored row", + "oracle": "screenshot", + "verify": "the published qa_designer_probe draws all three widgets", + "evidence": "screenshot" + }, + { + "clause": "summary report cells reconcile with the semantic layer — each status row's est_hours equals the dataset aggregate for that status", + "oracle": "api", + "verify": "row-by-row comparison of the rendered summary against a direct aggregate query", + "evidence": "comparison table" + }, + { + "clause": "matrix is a true pivot (rows × columns × measure cells) whose cells reconcile to the same aggregates", + "oracle": "api", + "verify": "3 spot-checked cells of showcase_status_priority_matrix equal the aggregate query values", + "evidence": "matrix screenshot + cell comparison" + }, + { + "clause": "joined report renders both blocks and their runtimeFilters actually partition: open_block totals + done_block totals == unfiltered totals", + "oracle": "api", + "verify": "sum comparison across the two blocks vs the unfiltered aggregate", + "evidence": "the three totals" + }, + { + "clause": "the embedded report chart plots the bound dataset's measure — same numbers as the table above it", + "oracle": "screenshot", + "verify": "Hours by Status (Chart): bar heights correspond to the table's est_hours values", + "evidence": "screenshot + table values" + }, + { + "clause": "every ReportType variant is verified per-variant: summary, matrix, joined as reports; tabular via its ListView-lens home", + "oracle": "screenshot", + "verify": "one evidence artifact per variant, with tabular's captured on the showcase_task tabular ListView and annotated with the ADR-0021 Phase 2 rationale", + "evidence": "per-variant artifact set" + } + ], + "negative": [ + "a joined block nested inside blocks[] must be rejected at parse (block type enum is tabular|summary|matrix — no recursion); a silent save is a FAIL", + "a report or widget silently rendering while its dataset binding names nothing (blank-without-error) is a FAIL" + ], + "variants": ["tabular (via ListView lens)", "summary", "matrix", "joined"], + "automated": { "kind": "e2e", "ref": "packages/qa/dogfood/test/dashboard-designer-roundtrip.dogfood.test.ts" }, + "traps": ["hydration-race", "seed-data-thin"], + "source": [ + "packages/spec/src/ui/report.zod.ts (ReportType enum; block type enum excludes joined)", + "packages/spec/src/ui/dashboard.zod.ts (ADR-0021 dataset+dimensions+values single form)", + "packages/spec/src/ui/dataset.zod.ts", + "examples/app-showcase/src/ui/reports/index.ts (the four reports + Phase 2 tabular conversion note)", + "examples/app-showcase/test/coverage.test.ts ('covers every report type', tabular filtered)", + "packages/qa/dogfood/test/dashboard-designer-roundtrip.dogfood.test.ts" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: dataset-only authoring contract (ADR-0021) + ReportType variant matrix, pinned to the designer-roundtrip golden test", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "dashboards.empty-null-bucket-boundaries", + "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, + "priority": "P2", + "surface": "mixed", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "the Delivery Operations dashboard's global status filter + dateRange (created_at, last_90_days default) as the empty-window lever", + "a column with NULL group values for the NULL-bucket check (e.g. accounts without signed_on — the seed leaves prospects unset by design)" + ] + }, + "steps": [ + "run the two pinned parity suites first (rule 6 — don't re-prove what automation pins): packages/qa/dogfood/test/date-bucket-parity-conformance.test.ts and packages/qa/dogfood/test/empty-group-bucket-parity.test.ts, capturing their output", + "open Delivery Operations and set the dateRange to a custom window BEFORE any seeded created_at (e.g. a week in 2020) so every bound widget's result set is empty", + "screenshot the board in the empty-window state and capture the browser console", + "verify via the network trace that the dataset queries carried the empty window's bounds and returned empty/zero results (the emptiness is server-truth, not a render glitch)", + "reset the window; set the global status filter to a single status and then drill toward a single-datapoint shape (one bucket) on a trend chart; screenshot", + "query a NULL-carrying group via the data API (accounts grouped on a column where prospects are NULL) and record the bucket label shape returned", + "check the trend charts against the month-bucket ground truth: the line_created widget must show one point per seeded month, never a single collapsed bucket" + ], + "acceptance": [ + { + "clause": "an empty result renders a designed empty state — never a broken axis-only plot, a JS error, or stale previous data", + "oracle": "screenshot", + "verify": "empty-window screenshot shows empty states / zeroed KPI tiles; console capture has no errors; no widget still shows the pre-filter values", + "evidence": "screenshot + console log" + }, + { + "clause": "the emptiness is authoritative: the dataset queries carried the new bounds and the server returned empty — the repaint reflects a real re-query", + "oracle": "network", + "verify": "captured queries include the 2020 window bounds; responses are empty/zero", + "evidence": "the network trace" + }, + { + "clause": "KPI metric tiles show 0 (or an explicit empty marker) for an empty window — not the last non-empty value", + "oracle": "api", + "verify": "tile values equal a direct aggregate query with the same bounds (which returns 0/empty)", + "evidence": "tile screenshot + query result" + }, + { + "clause": "date buckets are identical whether the engine pushes SQL down or falls back in-memory, for every granularity a driver advertises", + "oracle": "test", + "verify": "date-bucket-parity-conformance.test.ts passes (the #3773 seam: epoch-ms datetimes once bucketed as NULL and collapsed trend charts to one bar)", + "evidence": "test run output" + }, + { + "clause": "a NULL group value produces ONE consistent bucket label shape across both aggregation paths", + "oracle": "test", + "verify": "empty-group-bucket-parity.test.ts passes (the #3839 seam: SQL NULL vs in-memory '(null)' — totals reconciled, the label's TYPE diverged)", + "evidence": "test run output" + }, + { + "clause": "single-datapoint charts render the lone mark, and the run's evidence NOTES the weakness instead of counting it as full proof", + "oracle": "screenshot", + "verify": "the one-bucket chart draws; the evidence entry carries the single-datapoint annotation per the trap vocabulary", + "evidence": "annotated screenshot" + } + ], + "negative": [ + "a trend chart collapsing every row into one bucket while the seed spans multiple months is the #3773 regression returned — FAIL, not 'thin data'", + "an empty window that leaves widgets showing previous (stale) values with no re-query in the trace is a FAIL" + ], + "automated": { "kind": "conformance", "ref": "packages/qa/dogfood/test/date-bucket-parity-conformance.test.ts" }, + "traps": ["single-datapoint", "seed-data-thin"], + "source": [ + "packages/qa/dogfood/test/date-bucket-parity-conformance.test.ts (#3773)", + "packages/qa/dogfood/test/empty-group-bucket-parity.test.ts (#3839)", + "examples/app-showcase/src/ui/dashboards/ops-dashboard.dashboard.ts (dateRange + global filter levers)", + "examples/app-showcase/src/data/seed/index.ts (prospects carry no signed_on — the deliberate NULL population)" + ], + "history": [ + { "revision": 1, "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" } + ] + }, + { + "id": "dashboards.global-filters-rescope", + "title": "Dashboard-level dateRange + global filters re-scope every bound widget through its OWN field mapping; opted-out widgets stay fixed", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "the Revenue Pulse dashboard (showcase_revenue_pulse) — the framework#2501 acceptance fixture: one dateRange (default field issued_on) + one region filter (default field region) driving invoice AND account widgets, account widgets re-mapping via filterBindings (dateRange→signed_on, region→sales_region), one KPI opted out of both (filterBindings false) as the all-time reference", + "seeded accounts spread across sales_region amer/emea/apac with signed_on dates (src/data/seed/index.ts)" + ] + }, + "steps": [ + "open Revenue Pulse (Showcase app → Analytics → Revenue Pulse) and capture the initial dataset queries for all widgets", + "record the opted-out KPI's value and the query set", + "change the region filter to a specific region (e.g. emea) and capture every re-issued dataset query", + "verify field mapping in the trace: invoice-bound widgets filter on THEIR field (region), account-bound widgets on sales_region", + "change the dateRange to a custom window and capture the re-queries: invoice widgets carry issued_on bounds, account widgets carry signed_on bounds", + "confirm the opted-out KPI issued NO re-query and shows the unchanged all-time value", + "cross-check one re-scoped widget's displayed aggregate against a direct API aggregate with the same filter", + "repeat the global-filter check on Delivery Operations: the Task Status select must re-scope the KPI hero tiles, composing with each tile's own per-widget filter", + "screenshot before/after each filter change" + ], + "acceptance": [ + { + "clause": "changing the region filter re-issues server queries for every bound widget, each mapped to the widget's OWN field (region vs sales_region)", + "oracle": "network", + "verify": "the trace shows re-queries with the selected region on the correct per-widget field, for both objects", + "evidence": "the network trace, per widget" + }, + { + "clause": "changing the dateRange re-scopes both objects through their own date fields (issued_on vs signed_on)", + "oracle": "network", + "verify": "re-queries carry the custom window's bounds on the mapped field per widget", + "evidence": "the network trace" + }, + { + "clause": "the opted-out KPI (filterBindings: false) neither re-queries nor changes value — the opt-out is real", + "oracle": "network", + "verify": "no query for that widget after either filter change; displayed value identical before/after", + "evidence": "trace absence + before/after screenshots" + }, + { + "clause": "displayed aggregates match server truth under the active filters", + "oracle": "api", + "verify": "one re-scoped widget's value equals a direct aggregate query with the same region+window", + "evidence": "the comparison" + }, + { + "clause": "date-scoped account charts exclude accounts with no signed_on (prospects) — absence of a date excludes the row, by design", + "oracle": "api", + "verify": "the account-side aggregate under any date window excludes NULL-signed_on accounts; count matches the API query", + "evidence": "query comparison" + }, + { + "clause": "on Delivery Operations, the global status filter composes with per-widget filters — an at-risk KPI tile under a global status selection shows the intersection, verified against the API", + "oracle": "api", + "verify": "tile value equals the aggregate with BOTH filters applied", + "evidence": "tile screenshot + query result" + } + ], + "negative": [ + "a filter change that repaints without a server re-query (client-side cosmetic filtering) is a FAIL — the network trace is the oracle, not the repaint", + "the opted-out KPI drifting after a filter change is a FAIL (the fixed reference is the point of the opt-out)" + ], + "traps": ["hydration-race", "seed-data-thin"], + "source": [ + "examples/app-showcase/src/ui/dashboards/revenue-pulse.dashboard.ts (framework#2501 / objectui#2578 acceptance fixture)", + "examples/app-showcase/src/ui/dashboards/ops-dashboard.dashboard.ts", + "examples/app-showcase/src/data/seed/index.ts (sales_region + signed_on seeding, prospects unset by design)", + "examples/app-showcase/src/coverage.ts (dashboard kind notes: revenue-pulse demonstrates dashboard-level filters)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: dashboard-level filter re-scoping with per-widget filterBindings, both-sides (bound vs opted-out) verification", "ref": "claude/platform-test-checklist-ocwugl" } + ] } ] } diff --git a/docs/qa/platform-checklist/areas/i18n.json b/docs/qa/platform-checklist/areas/i18n.json index a57c96d6a6..9086c9c7f5 100644 --- a/docs/qa/platform-checklist/areas/i18n.json +++ b/docs/qa/platform-checklist/areas/i18n.json @@ -8,57 +8,394 @@ "title": "zh-CN notification is localized, deep-links localized, and mark-as-read clears", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "browser", - "personas": ["zh-CN workspace user receiving a collab.assignment notification"], + "personas": ["zh-CN workspace user receiving a collab.assignment notification", "en workspace user (control for per-recipient localization)"], + "fixtures": { + "app": "showcase", + "requires": [ + "the showcase config declares i18n.supportedLocales ['en', 'zh-CN'] (examples/app-showcase/objectstack.config.ts) — the zh-CN session is stock, no extra fixture", + "a second user to assign work to, so the assignment notification has a recipient distinct from the actor" + ] + }, "steps": [ - "trigger an assignment notification on a zh-CN workspace under os dev/standalone", - "open the bell, click through, mark as read", - "capture the mark-read request" + "boot the showcase under os dev/standalone, sign in as admin, and set the recipient user's locale to zh-CN", + "trigger a collab.assignment notification by assigning a seeded showcase_task to the zh-CN recipient (edit the task's assignee field)", + "sign in as the recipient (own browser context — never a shared tab), open the bell, and screenshot the notification entry", + "click through the notification's deep link and screenshot the target detail page", + "capture the mark-as-read request (POST /api/v1/notifications/read) and the unread-count reads before and after", + "reload the page fully and re-read the unread count from the server", + "repeat the trigger for an en-locale recipient and capture that bell entry as the control" ], "acceptance": [ { - "clause": "title and detail page render in the recipient locale", + "clause": "the bell entry's title renders in the recipient locale (zh-CN)", + "oracle": "screenshot", + "verify": "bell screenshot shows the assignment title in zh-CN, no raw message key", + "evidence": "bell screenshot" + }, + { + "clause": "the deep-link target renders in zh-CN — the click-through lands on a localized page, not a mixed-language one", "oracle": "screenshot", - "verify": "bell entry + detail page screenshots in zh-CN", - "evidence": "screenshots" + "verify": "detail-page screenshot in zh-CN (translated object label, field labels, section labels)", + "evidence": "detail screenshot" }, { "clause": "mark-as-read actually clears the unread state — the notifications REST routes must be mounted on the server actually serving os dev (the #3362 dispatcher-only registration made the console 404 here while unit tests stayed green)", "oracle": "network", "verify": "POST /api/v1/notifications/read returns 2xx on the running server and a fresh unread-count read drops", "evidence": "the network trace + before/after unread count" + }, + { + "clause": "the cleared state is authoritative — it survives a full reload, proving the server persisted it rather than the client repainting", + "oracle": "api", + "verify": "after a hard reload, the server's unread count still reflects the read (no resurrection of the cleared item)", + "evidence": "post-reload unread-count read" + }, + { + "clause": "localization is per-recipient, not session-global: the same event notifies the en control user in en", + "oracle": "screenshot", + "verify": "the en recipient's bell entry for an equivalent assignment renders in English", + "evidence": "control-user bell screenshot" } ], - "traps": ["dispatcher-vs-hono-route"], - "source": ["#3358 §7", "#3362", "#3354"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; the #3362 route-seam regression is the reason the oracle is a live-server network trace, never a unit test", "ref": "#3358" }] + "negative": [ + "a mark-as-read that returns 404/405 (routes mounted only on the dispatcher, not the live Hono server) is the #3362 regression — FAIL, and unit-test greenness is not a defense" + ], + "traps": ["dispatcher-vs-hono-route", "shared-browser-tab"], + "source": ["#3358 §7", "#3362", "#3354", "examples/app-showcase/objectstack.config.ts (supportedLocales)"], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; the #3362 route-seam regression is the reason the oracle is a live-server network trace, never a unit test", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "i18n.studio-follows-app-locale", "title": "Studio follows the in-app locale switch — no mixed-language session", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", "surface": "browser", "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "the platform-owned zh-CN metadata-form translations (the metadataForms translation group is owned and translated by platform-objects — scripts/check-i18n-coverage.mjs folds that baseline away because it is platform-owned)" + ] + }, "steps": [ - "switch the in-app locale", - "navigate into Studio / metadata-admin surfaces" + "sign in as admin on the showcase, switch the in-app locale to zh-CN, and confirm the app shell followed (nav in zh-CN)", + "navigate into Studio / metadata-admin: open the object editor for showcase_project and the dashboard designer for showcase_chart_gallery", + "screenshot the object editor's form — its labels/sections come from the metadataForms translation group (packages/spec/src/system/translation.zod.ts: metadataForms..label / sections / fields)", + "screenshot a metadata list surface showing relative dates ('x 天前'-style) in the switched locale", + "reload the browser fully and re-screenshot one Studio surface — the locale choice must survive the reload", + "switch back to en and re-screenshot the same two Studio surfaces" ], "acceptance": [ { - "clause": "Studio surfaces render in the switched locale (including relative dates); no mixed-language session", + "clause": "Studio's metadata-editor forms render in the switched locale via the metadataForms translation group", "oracle": "screenshot", - "verify": "post-switch Studio screenshots show the target locale consistently", - "evidence": "screenshots" + "verify": "object-editor screenshot shows zh-CN section and field labels (e.g. 基础信息-style section labels per the schema's own example), not English", + "evidence": "object-editor screenshot" + }, + { + "clause": "no mixed-language session: a single Studio screenshot contains no untranslated declared string sitting next to translated ones", + "oracle": "screenshot", + "verify": "review each captured Studio screenshot for locale consistency of declared (translatable) strings; user data (record values) is exempt", + "evidence": "the annotated screenshots" + }, + { + "clause": "relative dates follow the locale", + "oracle": "screenshot", + "verify": "list/timeline timestamps render zh-CN relative forms after the switch", + "evidence": "screenshot" + }, + { + "clause": "the locale choice persists across a full reload — it is stored, not a transient client state", + "oracle": "screenshot", + "verify": "post-reload Studio screenshot is still zh-CN without re-selecting", + "evidence": "post-reload screenshot" + }, + { + "clause": "switching back to en restores English on the same surfaces — the switch is symmetric, not a one-way ratchet", + "oracle": "screenshot", + "verify": "the return-to-en screenshots show the same surfaces fully in English", + "evidence": "return-trip screenshots" } ], + "negative": [ + "a Studio surface staying English after the app switched (mixed session) is the FAIL this item exists for — check against a fresh objectui build before filing (the vendored /_console bundle may be stale)" + ], "traps": ["stale-console-bundle"], - "source": ["#3358 §7"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }] + "source": [ + "#3358 §7", + "packages/spec/src/system/translation.zod.ts (metadataForms group + resolveMetadataFormLabels convention)", + "scripts/check-i18n-coverage.mjs (platform metadata-form baseline is platform-objects-owned)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "i18n.surface-matrix", + "title": "Every translatable surface localizes on a zh-CN session — one pass over the full translation-group vocabulary", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["admin on a zh-CN session"], + "fixtures": { + "app": "showcase", + "requires": [ + "the showcase translation bundle (examples/app-showcase/src/system/translations/index.ts) — it deliberately covers EVERY field surfaced as a list column so a zh-CN list never mixes locales", + "platform-objects' own bundles for sys_ object labels and Studio metadataForms (platform-owned per scripts/check-i18n-coverage.mjs)" + ], + "knownGaps": [ + "the showcase's declared surface carries FROZEN untranslated debt, ratcheted by scripts/check-i18n-coverage.mjs + i18n-coverage-baseline.json — expect some declared strings to legitimately fall back to en; the per-surface check is 'translated keys render translated, untranslated keys render the SOURCE label (never a raw dotted key)', not 100% coverage" + ] + }, + "steps": [ + "before the browser: read the served translation metadata (GET /api/v1/meta/translation, or the bundle source) and note, per variant surface, at least one key that IS translated to zh-CN — expectations derive from data, not vibes", + "sign in as admin, switch to zh-CN, and walk one surface per variant, screenshotting each:", + "nav labels — the Showcase app sidebar groups/nodes (apps..navigation..label group)", + "list headers — the showcase_task list columns (objects..fields..label; the bundle translates every surfaced column incl. status/priority OPTION labels)", + "form labels + placeholders on a task edit form (fields group), and select options in the form and list filters (fields..options)", + "an action dialog — trigger an object action with confirmText/successMessage and capture the confirm prompt and the success toast (_actions group: label/confirmText/successMessage)", + "a view empty state — open a view/slice with zero rows and capture emptyState.title/message (_views..emptyState)", + "detail-page section labels (_sections group)", + "a dashboard — Chart Gallery's title and widget titles (dashboards..label / widgets..title)", + "a page — a seeded page's header title/subtitle (pages. group)", + "a Settings namespace — title, group titles, key labels, and the source badges (settings. + settingsCommon.sourceLabels)", + "Studio metadata forms (metadataForms group — overlaps i18n.studio-follows-app-locale; a pinned pass there may be cited)", + "a sys_ object surface — e.g. the All Users list showing sys_user's translated label (platform-objects-owned bundle)", + "record a per-variant verdict table with the screenshot evidence" + ], + "acceptance": [ + { + "clause": "every variant surface renders its known-translated key in zh-CN — verified per-variant, no surface inferred from a sibling", + "oracle": "screenshot", + "verify": "one screenshot per variant showing the pre-identified translated key rendered in zh-CN", + "evidence": "per-variant screenshot set + verdict table" + }, + { + "clause": "expectations are grounded in server truth first: the translation metadata read names which keys are translated, and the screenshots are judged against THAT list", + "oracle": "api", + "verify": "the translation read (or bundle source) is captured and each per-variant expectation cites its key", + "evidence": "the translation read + key map" + }, + { + "clause": "fallback is honest: an untranslated declared string renders its SOURCE-language label — never a raw dotted key path, never an empty cell", + "oracle": "screenshot", + "verify": "spot-check at least two known-untranslated keys (from the coverage baseline debt); each shows the en label, no 'objects.x.fields.y.label' literals anywhere in the session", + "evidence": "the fallback screenshots" + }, + { + "clause": "select OPTION labels localize everywhere they appear: list cells, form selects, and list filters all show the translated option label for the same stored value", + "oracle": "screenshot", + "verify": "the showcase_task status/priority options (translated in the bundle) render zh-CN in all three places", + "evidence": "the three screenshots" + }, + { + "clause": "a list of a fully-covered object never mixes locales — the bundle's own contract (it translates every surfaced column precisely to prevent 状态 next to 'Assignee')", + "oracle": "screenshot", + "verify": "the showcase_task list header row is 100% zh-CN", + "evidence": "list screenshot" + }, + { + "clause": "action dialog copy (confirm prompt, success toast) comes from the _actions translation, and the result renders after the action actually executed", + "oracle": "network", + "verify": "the action's request fired and returned 2xx while the zh-CN confirm/success strings were shown — the toast is attached to a real server round-trip", + "evidence": "network trace + dialog/toast screenshots" + } + ], + "negative": [ + "a raw translation key (dotted path) rendered anywhere is a FAIL — that is the silent-strip failure surfacing to the user", + "a surface whose group the spec declares (e.g. _sections) showing NO translation while the bundle carries one for it is a FAIL against the resolver, not a coverage gap" + ], + "variants": [ + "nav labels (apps.navigation)", + "list headers (objects.fields.label)", + "select option labels (objects.fields.options)", + "form labels/placeholders (objects.fields)", + "action dialogs (objects._actions: label/confirmText/successMessage)", + "view empty states (objects._views.emptyState)", + "detail section labels (objects._sections)", + "dashboards (dashboards.label / widgets.title)", + "pages (pages.label/title/subtitle)", + "settings (settings. + settingsCommon.sourceLabels)", + "Studio metadata forms (metadataForms)", + "sys_ object labels (platform-objects bundles)", + "relative dates (locale formatting, not a translation group)" + ], + "traps": ["stale-console-bundle", "hydration-race", "wrong-panel"], + "source": [ + "packages/spec/src/system/translation.zod.ts (translationDataShape — the authoritative group vocabulary: objects/_views/_actions/_sections, apps.navigation, messages, globalActions, dashboards, pages, settings, metadataForms, settingsCommon)", + "examples/app-showcase/src/system/translations/index.ts (full-column coverage rationale)", + "content/docs/ui/translations.mdx", + "scripts/check-i18n-coverage.mjs + scripts/i18n-coverage-baseline.json (frozen-debt ratchet)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new matrix item: per-surface localization pass over the spec's full translation-group vocabulary, grounded in the showcase bundle and the coverage ratchet", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "i18n.strict-translation-key-rejection", + "title": "Unknown, legacy-dialect, and retired translation keys are rejected loudly at BOTH authoring doors — never silently stripped", + "since": "v17", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "runtime metadata authoring enabled (the `translation` item door: the Studio translation editor or the metadata API against /api/v1/meta/translation)" + ] + }, + "steps": [ + "item door — via the metadata API, attempt to save a zh-CN `translation` item carrying each variant in turn (one save per variant), always alongside one VALID key (e.g. objects.showcase_task.label) so the rejection provably targets the bad key, not the document", + "capture the exact error text for each attempted save", + "after each rejection, read the item back and record that nothing (including the valid key) was persisted by the failed save", + "bundle door — in a scratch script inside the worktree, call defineTranslation / defineTranslationBundle from @objectstack/spec with the same variants and capture each parse error (this is the door #3778's item-only guard missed; #4001's strict shapes must cover both)", + "verify the alias suggestion channel: a near-miss key (helpText on a FIELD translation, label on a WIDGET translation) must be rejected WITH the did-you-mean pointing at the declared spelling (help; title)", + "verify the guidance channel: legacy object-first keys (o, app, nav, dashboard, errors, _meta) and retired validationMessages must carry their prescription (where the content belongs now, or that it has no home), not just 'unrecognized key'", + "save a correct version of the same content and confirm it persists and resolves (the gate rejects keys, not translations)" + ], + "acceptance": [ + { + "clause": "every variant is REJECTED at the item door with an error naming the surface and echoing the offending key", + "oracle": "api", + "verify": "each save returns a validation error whose text contains the offending key and the surface name ('this translation', 'this field translation', …)", + "evidence": "per-variant error texts" + }, + { + "clause": "the same variants are rejected at the bundle door — the two doors cannot diverge (the #3778/#4522 asymmetry: covered at one door, open at the other)", + "oracle": "test", + "verify": "defineTranslation/defineTranslationBundle throw for every variant with equivalent error content", + "evidence": "the scratch-script output" + }, + { + "clause": "near-miss keys get a did-you-mean naming the declared spelling (aliases channel), so the author's next action is a rename", + "oracle": "log", + "verify": "helpText→help (field), label→title (dashboard widget) suggestions appear in the respective errors", + "evidence": "the two error texts" + }, + { + "clause": "legacy-dialect and retired keys carry their PRESCRIPTION (guidance channel): where the content now belongs, or an explicit 'no replacement' (errors/validationMessages: author the message on object.validations[].message)", + "oracle": "log", + "verify": "the o/app/nav/dashboard errors point at objects./apps./navigation/dashboards (plural); validationMessages cites #4667/ADR-0049 and the rule-message home", + "evidence": "the error texts" + }, + { + "clause": "a failed save persists NOTHING — the valid sibling key must not have been half-saved", + "oracle": "api", + "verify": "reading the translation item after each rejected save shows the pre-attempt state", + "evidence": "the read-back responses" + }, + { + "clause": "the corrected document saves and RESOLVES — the translated label actually renders on a zh-CN session afterwards", + "oracle": "screenshot", + "verify": "the key rejected-then-corrected (objects.showcase_task.label or similar) shows its zh-CN value in the UI", + "evidence": "post-fix screenshot" + } + ], + "negative": [ + "a 2xx save of ANY variant is a FAIL even if nothing renders wrong afterwards — silent stripping is indistinguishable from 'not translated yet' forever, which is the exact failure #4001 closed", + "a rejection that names only 'unknown key' with no surface, no echo, and no suggestion is a PARTIAL — the diagnostic contract is part of the item" + ], + "variants": [ + "legacy object-first: o", + "legacy object-first: app", + "legacy object-first: nav", + "legacy object-first: dashboard", + "legacy with no replacement: errors", + "retired: validationMessages (#4667)", + "legacy: _meta", + "alias near-miss: helpText on a field translation (→ help)", + "alias near-miss: label on a dashboard widget translation (→ title)", + "hallucinated group: a wholly invented top-level key" + ], + "traps": ["dispatcher-vs-hono-route"], + "source": [ + "packages/spec/src/system/translation.zod.ts (TRANSLATION_HISTORY, LEGACY_OBJECT_FIRST_KEYS, TRANSLATION_KEY_GUIDANCE, strict shapes at both doors — #4001, #3778, #4667)", + "packages/spec/src/shared/strict-object.ts (surface/aliases/guidance rejection contract)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: strict-key rejection at both translation doors, variants from the spec's own legacy/guidance/alias tables", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "i18n.build-gates-hold", + "title": "The i18n build gates hold and can go red: bundle drift, undeclared extract keys, and the coverage ratchet", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "build", + "personas": ["ci"], + "fixtures": { + "app": "any", + "requires": [ + "a BUILT workspace — both gates run the built CLI and now hard-fail with a named prerequisite verdict when it is missing (#5217/#5862) instead of blaming an innocent config" + ] + }, + "steps": [ + "build the workspace, then run node scripts/check-i18n-bundles.mjs and capture the output (two distinct verdicts: bundle drift; undeclared extract key)", + "run node scripts/check-i18n-bundles.mjs --self-test and node scripts/check-i18n-coverage.mjs --self-test — the classifiers must be provably able to go red (#4690: a gate observed only green is indistinguishable from a gate matching nothing)", + "run node scripts/check-i18n-coverage.mjs against the baseline", + "prove the drift gate bites: perturb one committed bundle value in the worktree, re-run check-i18n-bundles.mjs, capture the red verdict, then revert the perturbation", + "prove the ratchet bites: the baseline check must fail on an INCREASED untranslated count — verify by inspecting that the current counts equal the baseline (growth would fail), and capture the baseline comparison output", + "in an UNBUILT state (or by consulting the gates' own prerequisite check), confirm the failure mode is the named build-prerequisite verdict, not nine phantom bundle problems" + ], + "acceptance": [ + { + "clause": "check-i18n-bundles passes on a clean tree, reporting its two verdict classes separately", + "oracle": "build", + "verify": "exit 0 with the drift and undeclared-key sections both clean", + "evidence": "gate output" + }, + { + "clause": "both gates' --self-test proves the red path exists", + "oracle": "build", + "verify": "each --self-test run exercises its classifiers against fixed samples and passes", + "evidence": "self-test outputs" + }, + { + "clause": "the drift gate actually bites: a perturbed bundle turns the gate red naming the drifted package", + "oracle": "build", + "verify": "the perturbation run exits non-zero citing the perturbed bundle; the revert run is green again", + "evidence": "the red output + the post-revert green output" + }, + { + "clause": "the coverage ratchet holds the frozen debt: counts match the committed baseline, and the gate's contract fails growth", + "oracle": "build", + "verify": "check-i18n-coverage exits 0 with counts equal to scripts/i18n-coverage-baseline.json", + "evidence": "gate output + baseline diff" + }, + { + "clause": "an unbuilt workspace produces the HARD named prerequisite failure ('measured nothing'), never a skip and never phantom per-config errors", + "oracle": "build", + "verify": "the prerequisite verdict names the missing build, not an i18n config", + "evidence": "the captured failure output" + } + ], + "negative": [ + "a green run after the deliberate bundle perturbation is a FAIL of the gate itself — file against the gate, and do not trust any of its other verdicts that sweep", + "--write printing 'regenerated' while writing nothing (the historical unbuilt-CLI shape) is a FAIL" + ], + "source": [ + "scripts/check-i18n-bundles.mjs (#4804 undeclared-key verdict, #5217 prerequisite check, #4690 self-test rationale)", + "scripts/check-i18n-coverage.mjs (#3370 declared-surface ratchet, #5862 prerequisite check)", + "scripts/i18n-coverage-baseline.json" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: the i18n build lane — drift gate, undeclared-key gate, ratchet, and their provable red paths", "ref": "claude/platform-test-checklist-ocwugl" } + ] } ] } From a93032534e8fe9d908e7a4189d73e8674d1cb896 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 13:32:43 +0000 Subject: [PATCH 04/14] =?UTF-8?q?feat(qa):=20=E6=B7=B1=E5=8C=96=20identity?= =?UTF-8?q?-auth/automation/access-security/records-forms=20=E5=9F=9F(?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E7=82=B9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Progressive deepening checkpoint — validator green at 11 areas / 74 items. identity-auth complete (auth-method matrix from AuthPluginConfigSchema + PUBLIC_AUTH_FEATURES, delegated-admin invitation gates ADR-0105 D8, admin lifecycle ops with audit attribution); automation and access-security rewritten to the deep contract (ExecutionStepLogSchema region tags, runRlsProofs delegation with skip-audit clause, readonlyWhen locks, owner-anchor forge/transfer/disown probes); records-forms in progress by its agent. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01YD9f6FYyMraUWYeJf53V43 --- .../areas/access-security.json | 581 +++++++++- .../platform-checklist/areas/automation.json | 643 ++++++++++- .../areas/identity-auth.json | 391 ++++++- .../areas/records-forms.json | 1018 +++++++++++++++-- 4 files changed, 2469 insertions(+), 164 deletions(-) diff --git a/docs/qa/platform-checklist/areas/access-security.json b/docs/qa/platform-checklist/areas/access-security.json index 09a4fd71ac..871dec7aa9 100644 --- a/docs/qa/platform-checklist/areas/access-security.json +++ b/docs/qa/platform-checklist/areas/access-security.json @@ -8,102 +8,222 @@ "title": "Row-level security: restricted member sees only their rows; admin sees all", "since": "v15", "status": "active", - "revision": 1, + "revision": 2, "priority": "P0", "surface": "api", - "personas": ["admin", "restricted member on an OWD-private object"], + "personas": ["admin (first sign-in dev admin, platform posture)", "two plain members (everyone baseline = showcase_member_default only)"], + "fixtures": { + "app": "showcase", + "requires": ["showcase_private_note (OWD private, member_default grants create/read/edit)", "two fresh sign-ups, e.g. rls-a@verify.test / rls-b@verify.test"] + }, "steps": [ - "as the restricted member, list an OWD-private object over the data API", - "as admin, list the same object", - "compare against the ground-truth row set" + "boot showcase isolated (own port + file DB per dogfood skill §0); sign in as admin; sign up members A and B (fresh emails) — both hold only the everyone baseline showcase_member_default", + "as A: POST /api/v1/data/showcase_private_note {\"title\": \"A note 1\"} and a second note; as B: POST one note — capture the returned ids", + "as A: GET /api/v1/data/showcase_private_note (list); as B: same; capture both record sets", + "as A: GET /api/v1/data/showcase_private_note/ (foreign by-id read); as A: PATCH /api/v1/data/showcase_private_note/ {\"body\": \"forged\"} (foreign by-id write)", + "as admin: GET /api/v1/data/showcase_private_note and confirm the full row set (A's + B's rows) is visible — the entitled side of the gate", + "re-read B's note as admin and confirm the forged PATCH left it byte-identical", + "run the app-agnostic sweep: runRlsProofs from @objectstack/verify (objectstack verify) over every showcase object; capture the per-object verdicts" ], "acceptance": [ { - "clause": "member reads return only owned/shared rows; admin reads return the full set — BOTH sides verified, absence and presence", + "clause": "A's list contains exactly A's notes — none of B's (presence AND absence, from the server row set, not the UI)", + "oracle": "api", + "verify": "GET /api/v1/data/showcase_private_note as A: records[] titles include 'A note 1', exclude B's title; symmetric check as B", + "evidence": "both list responses" + }, + { + "clause": "foreign by-id READ is denied server-side (non-2xx) — owner isolation holds at record granularity, not just list filtering", + "oracle": "api", + "verify": "GET /api/v1/data/showcase_private_note/ as A answers non-200 (403 PERMISSION_DENIED or a not-found-shaped denial — capture which; both are honest owner isolation, a 200 is the failure)", + "evidence": "status + body" + }, + { + "clause": "foreign by-id WRITE is denied and the row is unchanged (the #1994 'you can't mutate what you can't see' invariant)", "oracle": "api", - "verify": "row-set comparison per persona; the auto-derived RLS proofs in @objectstack/verify (runRlsProofs) cover this app-agnostically — rls-consistent verdict required, rls-hole is a FAIL", - "evidence": "the two listings + the verify run verdict" + "verify": "PATCH as A answers >=400 AND the admin re-read of B's note shows the pre-attempt body — a 4xx with a mutated row is still a FAIL", + "evidence": "PATCH response + admin re-read" + }, + { + "clause": "admin (platform posture) reads the full set — the entitled side of the same gate (both sides, RUNNER rule 4 / ADR-0057 D10)", + "oracle": "api", + "verify": "admin GET list contains every id created in this run", + "evidence": "admin listing" + }, + { + "clause": "runRlsProofs reports rls-consistent (or member-visible on deliberately public objects) for every non-skipped showcase object; any rls-hole verdict is a FAIL and files an issue", + "oracle": "test", + "verify": "run runRlsProofs(stack, adminToken, memberToken, config) from packages/verify/src/rls.ts; summary.holes must be 0", + "evidence": "formatRlsReport output" + }, + { + "clause": "skipped objects in the verify report are each explainable (no plain-text probe field / blocked fixture) — a skip hiding a hole is the #3415 seed-defect class", + "oracle": "test", + "verify": "for each status:'skipped' row, the detail names the benign reason; spot-check one skipped object by hand with the by-id read/write probe", + "evidence": "report detail lines + the spot-check trace" } ], - "automated": { "kind": "verify", "ref": "packages/verify/src/rls.ts (objectstack verify)" }, - "source": ["packages/verify", "ADR-0057 D10"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — standing P0, delegating the systematic sweep to @objectstack/verify and keeping the persona spot-check manual", "ref": "#3358" }] + "negative": ["the foreign by-id write must not silently succeed: a 2xx on the forged PATCH, or a 4xx that still mutated the row (verified by the admin re-read), is a FAIL even though every list looked correctly filtered"], + "traps": ["wrong-persona"], + "automated": { "kind": "verify", "ref": "packages/verify/src/rls.ts (objectstack verify) + packages/qa/dogfood/test/showcase-private-owd.dogfood.test.ts" }, + "source": ["packages/verify/src/rls.ts", "ADR-0057 D10", "packages/qa/dogfood/test/showcase-private-owd.dogfood.test.ts", "authz-conformance.matrix.ts rows rls-read / rls-by-id-write"], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — standing P0, delegating the systematic sweep to @objectstack/verify and keeping the persona spot-check manual", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "access-security.write-path-guards", "title": "Write-path guards: readonly strip, owner_id forge/transfer denied, bulk validation", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P0", "surface": "api", - "personas": ["admin", "non-admin member (owner forge is only meaningful non-privileged)"], + "personas": ["admin", "two non-admin members (owner forge is only meaningful non-privileged; everyone baseline grants private-note create/edit and contact is reachable read-only, so use a member with showcase_contributor where create is needed)"], + "fixtures": { + "app": "showcase", + "requires": ["showcase_contact.lead_score is the seeded readonly:true field (no defaultValue — stripped writes read back null)", "showcase_private_note carries the owner_id anchor", "showcase_invoice.tax_rate + line quantity/unit_price/product lock via readonlyWhen record/parent.status == 'paid'"] + }, "steps": [ - "REST-INSERT and UPDATE a readonly:true field; re-read the row", - "as the non-admin, attempt an insert forging another user's owner_id and an update transferring it", - "bulk-update a set that violates a validation rule" + "boot showcase isolated; admin session + two members Alice and Bob (fresh sign-ups); resolve their sys_user ids via the data API or objectql", + "as admin (contact create is admin-side in the stock matrix): POST /api/v1/data/showcase_contact {\"name\": \"RO probe\", \"email\": \"ro-probe@verify.test\", \"lead_score\": 10} — forging the readonly field on INSERT; GET the row back", + "PATCH the same contact with {\"lead_score\": 99}; GET again", + "as Alice: POST /api/v1/data/showcase_private_note {\"title\": \"planted\", \"owner_id\": \"\"} (insert forge); then POST a note with {\"owner_id\": \"\"} (explicit self-owner); then PATCH her own note with {\"owner_id\": \"\"} (transfer) and with {\"owner_id\": null} (disown)", + "as Alice: POST /api/v1/data/showcase_private_note/createMany with 3 records carrying no owner_id; GET them back and read owner_id on each", + "as admin: set an invoice to status 'paid', then PATCH its tax_rate and a line's quantity — the readonlyWhen lock (#3042 bulk half: updateMany over a set including the paid invoice's line)", + "bulk-update a set where some rows violate a validation rule (e.g. showcase_invoice_line quantity below min 0 on some rows only) via POST /api/v1/data/showcase_invoice_line/updateMany; read the per-row outcome" ], "acceptance": [ { - "clause": "readonly fields are stripped on INSERT and UPDATE (value unchanged server-side)", + "clause": "readonly field is stripped on INSERT — stored value is the default (null for lead_score), never the payload's; the create itself still succeeds (#3043 admit-and-strip, not reject)", "oracle": "api", - "verify": "post-write reads show the readonly field untouched", - "evidence": "write payloads + post-write reads" + "verify": "POST answers 2xx; GET /api/v1/data/showcase_contact/ shows lead_score null; response header/body droppedFields advertises the strip (#3431)", + "evidence": "payload + read + droppedFields" }, { - "clause": "owner_id forge and transfer are denied for non-privileged users; bulk inserts stamp the current user", + "clause": "readonly field is stripped on UPDATE — value unchanged after PATCH (#2948/#3003; note: a deployment opting into strictReadonlyWrites refuses instead with ERR_READONLY_FIELD_REJECTED per #5126 — stock showcase is strip)", "oracle": "api", - "verify": "forge/transfer attempts rejected; empty-owner bulk insert rows land stamped with the caller", - "evidence": "the rejections + stamped rows" + "verify": "before/after GETs identical on lead_score", + "evidence": "the reads" }, { - "clause": "bulk update runs validation rules per row (the updateMany call-site gap of #3106 must not reproduce)", + "clause": "owner forge on INSERT is DENIED for the member and the row is not created", "oracle": "api", - "verify": "a bulk update violating a rule is rejected/skipped per row, not silently applied", - "evidence": "the bulk response + row reads" + "verify": "POST with owner_id= as Alice answers >=400; a system-context count of notes titled 'planted' is 0", + "evidence": "response + filtered count" + }, + { + "clause": "owner transfer and disown on UPDATE are DENIED; owner unchanged — while explicit SELF-owner insert succeeds (the guard gates on identity, not on the key's presence)", + "oracle": "api", + "verify": "PATCH owner_id= and owner_id=null both >=400 with owner_id re-reading as Alice's id; POST with owner_id= is 2xx and persists", + "evidence": "responses + re-reads" + }, + { + "clause": "empty-owner bulk insert stamps the calling member on every row", + "oracle": "api", + "verify": "all createMany rows read back owner_id == Alice's sys_user id", + "evidence": "the reads" + }, + { + "clause": "readonlyWhen locks hold at the API on the locked state: paid invoice's tax_rate and its lines' quantity/unit_price/product are not writable, and a bulk update touching any locked row drops the field for the batch (#3042)", + "oracle": "api", + "verify": "post-write reads show the locked fields unchanged on the paid invoice and its lines", + "evidence": "write payloads + re-reads" + }, + { + "clause": "bulk update evaluates validation rules PER ROW (the #3106 updateMany gap must not reproduce): violating rows rejected/skipped, compliant rows applied — one mixed batch shows both outcomes", + "oracle": "api", + "verify": "updateMany response distinguishes per-row results; re-reads confirm compliant rows changed and violating rows did not", + "evidence": "bulk response + row reads" } ], - "automated": { "kind": "dogfood", "ref": "packages/qa/dogfood/test/owner-anchor-and-bulk-writes.dogfood.test.ts (+ showcase-static-readonly)" }, + "negative": ["run the forge/transfer clauses as ADMIN too and confirm they SUCCEED where legitimately privileged — the guard must gate on privilege, not break the admin path (wrong-persona trap, both sides)", "isSystem writes must still set readonly fields (A1's isSystem carve-out): verify via an objectql write with context {isSystem:true} that lead_score IS settable system-side"], + "automated": { "kind": "dogfood", "ref": "packages/qa/dogfood/test/owner-anchor-and-bulk-writes.dogfood.test.ts (+ showcase-static-readonly.dogfood.test.ts, showcase-readonly-when-parent.dogfood.test.ts)" }, "traps": ["wrong-persona"], - "source": ["#3358 §9", "release-15.1 plan §A", "#3106"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — merged from #3358 §9 and the 15.1 A-group rows these dogfood tests pin", "ref": "#3358" }] + "source": ["#3358 §9", "release-15.1 plan §A1–A4", "#3106", "#5126", "examples/app-showcase/src/data/objects/contact.object.ts (lead_score)", "examples/app-showcase/src/data/objects/invoice.object.ts (readonlyWhen)"], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — merged from #3358 §9 and the 15.1 A-group rows these dogfood tests pin", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "access-security.record-access-explain", "title": "Record-grained access explain shows per-layer attribution and a verdict", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", - "surface": "browser", - "personas": ["admin"], + "surface": "mixed", + "personas": ["admin (drives the panel and the explain API)", "auditor (viewAllRecords persona whose access is explained)", "plain member (the deny case)"], + "fixtures": { + "app": "showcase", + "requires": ["a showcase_private_note owned by a third user (the probe record)", "auditor member granted showcase_auditor (sys_user_permission_set row, as showcase-permission-zoo.dogfood.test.ts does)"] + }, "steps": [ - "in the access explain panel, pick user + object + record", - "read the layered result" + "boot showcase with the console; as admin create the probe: a member-owned showcase_private_note; grant a second member showcase_auditor", + "in the console's access explain panel pick user = the auditor, object = showcase_private_note, record = the probe; screenshot the layered result", + "call the API twin: POST /api/v1/security/explain (client security.explain; GET variant also mounted — rest-route-ledger security-explain family) with the same user/object/record; capture the JSON", + "as the auditor (real session): GET /api/v1/data/showcase_private_note/ — the impersonated-read cross-check", + "repeat panel + API explain for the PLAIN member (no auditor set) on the same record", + "as the plain member: GET the same record and capture the denial" ], "acceptance": [ { "clause": "the panel attributes access per layer (permission set → position → sharing → row rules) and renders a record.visible verdict naming the deciding layer", "oracle": "screenshot", - "verify": "screenshot shows all layers with the verdict; spot-check the verdict against an actual read as that user (server truth)", - "evidence": "screenshot + the impersonated read" + "verify": "screenshot shows all layers with the verdict", + "evidence": "screenshot" + }, + { + "clause": "for the auditor the deciding layer is the VAMA bypass, attributed to the showcase_auditor set (explain() reports the vama_bypass layer — the permission-zoo pinned shape)", + "oracle": "api", + "verify": "POST /api/v1/security/explain response names vama_bypass (or the equivalent layer key) with showcase_auditor as contributor", + "evidence": "explain JSON" + }, + { + "clause": "the explain verdict MATCHES the impersonated read, both ways: auditor verdict visible ∧ auditor GET 200; plain-member verdict not-visible ∧ plain-member GET non-200 (server truth outranks the panel)", + "oracle": "api", + "verify": "compare record.visible against the actual GET status per persona — any disagreement is a FAIL against explain", + "evidence": "explain JSONs + both GET traces" + }, + { + "clause": "the deny-side explain still answers 200 with a structured not-visible result naming the deciding layer — not EXPLAIN_FAILED, not an empty body", + "oracle": "api", + "verify": "plain-member explain response: 200, visible=false, deciding layer named; error code EXPLAIN_FAILED absent", + "evidence": "explain JSON" + }, + { + "clause": "panel and API twin agree (same layers, same verdict) — the console must render the server's explanation, not recompute its own", + "oracle": "api", + "verify": "field-by-field compare of the panel's displayed layers vs the POST /api/v1/security/explain body", + "evidence": "screenshot + JSON diff" } ], - "traps": ["hydration-race"], - "source": ["#3358 §5"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358, adding the impersonated-read cross-check", "ref": "#3358" }] + "negative": ["explain for the plain member must NOT show visible=true merely because the ADMIN is the one asking — the explanation is about the target user, not the caller (wrong-persona); cross-check with the plain member's own denied GET"], + "traps": ["hydration-race", "wrong-persona"], + "automated": { "kind": "dogfood", "ref": "packages/qa/dogfood/test/showcase-permission-zoo.dogfood.test.ts (explain vama_bypass case)" }, + "source": ["#3358 §5", "packages/rest/src/rest-route-ledger.ts (security-explain family)", "packages/plugins/plugin-security/src/explain-engine.ts"], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358, adding the impersonated-read cross-check", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "access-security.readonly-package-locks-studio", "title": "A read-only package actually locks Studio editing surfaces", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", - "surface": "browser", + "surface": "mixed", "personas": ["admin"], "steps": [ - "open the permission matrix / object designer on a read-only package" + "boot showcase with the console; open Studio → object designer and the permission matrix on an object belonging to a read-only (installed/locked) package", + "screenshot the surface; then read the DOM state of the edit affordances (checkboxes, Save)", + "attempt the edit through the UI anyway (click a checkbox / Save) and capture what the client does", + "forge the write directly: PUT /api/v1/meta/object/ with a trivial field change, as the same admin session", + "repeat the same PUT against a WRITABLE (draft/app-local) object to prove the guard discriminates by package writability, not by blanket denial" ], "acceptance": [ { @@ -111,10 +231,385 @@ "oracle": "dom", "verify": "after screenshot confirms render, assert disabled state on the controls (a badge alone is not a lock)", "evidence": "screenshot + disabled-state DOM read" + }, + { + "clause": "the SERVER refuses the same write: direct PUT /api/v1/meta/object/ on the read-only package answers 4xx with a ledgered metadata-protocol code (WRITABLE_PACKAGE_REQUIRED, or ITEM_LOCKED for _lock'd items) — UI absence never suffices (ADR-0057 D10)", + "oracle": "api", + "verify": "PUT response status >=400 and error.code ∈ {WRITABLE_PACKAGE_REQUIRED, ITEM_LOCKED} (packages/spec/src/api/error-code-ledger.zod.ts, @objectstack/metadata-protocol entry)", + "evidence": "PUT trace" + }, + { + "clause": "the same PUT against a writable object SUCCEEDS for the same admin — the lock keys on package writability, not on the route (both sides of the gate)", + "oracle": "api", + "verify": "writable-target PUT answers 2xx and a follow-up GET shows the change", + "evidence": "both traces" + }, + { + "clause": "the denied write leaves the packaged object byte-identical (persistence of the lock)", + "oracle": "api", + "verify": "GET /api/v1/meta/object/ before/after the denied PUT — identical", + "evidence": "the two reads" + } + ], + "negative": ["a UI that greys the controls while the direct PUT succeeds is a FAIL of this item even though the screenshot looks correct — record it as a server-guard gap, not a UI polish issue"], + "traps": ["stale-console-bundle", "hydration-race"], + "source": ["#3358 §5", "packages/spec/src/api/error-code-ledger.zod.ts (@objectstack/metadata-protocol codes)", "ADR-0010 §3.3 (_lock)"], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "access-security.crud-permission-matrix", + "title": "CRUD × permission-set matrix: every access-matrix.json row holds — allowed verbs succeed, withheld verbs 403, VAMA bounded", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P0", + "surface": "api", + "personas": ["admin", "one fresh member per permission set under test (set granted via a sys_user_permission_set row, as showcase-permission-zoo.dogfood.test.ts does)", "a second member as the foreign-row owner"], + "fixtures": { + "app": "showcase", + "requires": ["examples/app-showcase/access-matrix.json — it IS the expectation table (31 permission-set × object rows)", "seeded permission sets showcase_contributor/manager/executive/auditor/ops/member_default/guest_portal/field_ops_delegate (security bootstrap)"], + "knownGaps": ["every authenticated member ALSO holds the everyone baseline showcase_member_default additively (ADR-0090 D5) — the effective expectation per cell is the UNION of the tested set's row and the baseline's row for that object; compute the union before judging a cell, or a baseline-granted read will look like a matrix violation"] + }, + "steps": [ + "boot showcase isolated; admin session; for each permission set in access-matrix.json create a fresh member (unique email) and grant exactly that set by inserting the sys_user_permission_set row (system context), mirroring showcase-permission-zoo.dogfood.test.ts", + "as admin (or a helper member), seed one FOREIGN-owned probe row per object under test (e.g. a showcase_private_note, showcase_inquiry, showcase_invoice + line, showcase_announcement owned by someone other than the persona)", + "for every row of access-matrix.json drive all four verbs as that persona: POST /api/v1/data/ {minimal valid payload}, GET /api/v1/data/ + GET /api/v1/data//, PATCH /api/v1/data//, DELETE /api/v1/data// — capture status + error.code for each cell", + "for rows with viewAllRecords:true (auditor on showcase_inquiry/showcase_invoice/showcase_invoice_line/showcase_private_note): GET the foreign probe by id and list; for the SAME objects re-run as a persona whose row says viewAllRecords:false", + "for rows with modifyAllRecords:true (ops on showcase_announcement): PATCH the foreign-owned announcement; re-run the same PATCH as member_default (modifyAllRecords:false)", + "for the guest_portal row pair on showcase_inquiry (create:true, read:false): POST an inquiry as that persona, then GET the list and the created id", + "record the full verb × set × object verdict matrix and diff it against access-matrix.json (unioned with the baseline per the known gap above)" + ], + "acceptance": [ + { + "clause": "every allowed cell succeeds: for each access-matrix.json row, verbs marked true answer 2xx and the effect persists (created row readable, patched field re-reads changed, deleted row gone)", + "oracle": "api", + "verify": "per-cell status < 300 plus a follow-up read proving the effect", + "evidence": "the verdict matrix + spot re-reads" + }, + { + "clause": "every withheld cell is DENIED SERVER-SIDE with the ledgered code: verbs marked false answer 403 with error.code PERMISSION_DENIED (rest-server maps explicit security denials to 403 PERMISSION_DENIED) — UI absence never counts (ADR-0057 D10)", + "oracle": "api", + "verify": "per-cell status 403 and body code PERMISSION_DENIED; capture any cell answering a different code for triage", + "evidence": "the verdict matrix" + }, + { + "clause": "a denied CREATE leaves no row behind (persistence of the denial)", + "oracle": "api", + "verify": "system-context count of rows matching the denied payload's unique marker is 0", + "evidence": "filtered count" + }, + { + "clause": "viewAllRecords:true bypasses OWD/sharing on exactly the named objects — the auditor reads the foreign private note/inquiry/invoice/line by id AND in lists; a persona without the bit gets the foreign row neither way", + "oracle": "api", + "verify": "auditor GETs 200 with the probe present; the contrast persona's by-id GET non-200 and list excludes it", + "evidence": "both personas' traces" + }, + { + "clause": "modifyAllRecords:true grants foreign WRITE only where held: ops PATCHes anyone's announcement (public_read OWD, owner-writes baseline) with 2xx + persisted change; member_default's identical PATCH is denied and the row unchanged", + "oracle": "api", + "verify": "ops PATCH 2xx + re-read; member PATCH >=400 + unchanged re-read", + "evidence": "both traces + re-reads" + }, + { + "clause": "the write-only intake asymmetry holds: guest_portal's showcase_inquiry row (create:true, read:false) accepts the POST but denies reading it back — create must not imply read", + "oracle": "api", + "verify": "POST 2xx; subsequent GET list/by-id as the same persona non-200 or excludes the row", + "evidence": "POST + read traces" + }, + { + "clause": "every variant (permission set) is driven over EVERY object row access-matrix.json lists for it, and the run record carries one verdict per cell — a set skipped or an object row skipped makes the item at best partial", + "oracle": "api", + "verify": "verdict matrix dimensions match access-matrix.json entries (31 rows × 4 verbs at revision-time; recount from the file each run)", + "evidence": "the matrix artifact" + } + ], + "negative": ["run one denied cell per set as ADMIN and confirm it succeeds — proves the denial came from the persona's grants, not from a broken route (wrong-persona, both sides)", "any withheld cell answering 2xx is a FAIL even if the created/changed data looks harmless; silent success is the defect"], + "variants": ["showcase_contributor", "showcase_manager", "showcase_executive", "showcase_auditor", "showcase_ops", "showcase_member_default", "showcase_guest_portal", "showcase_field_ops_delegate"], + "automated": { "kind": "verify", "ref": "packages/verify/src/verify.ts (runCrudVerification) + packages/verify/src/rls.ts (runRlsProofs) — objectstack verify; persona-grained cells remain manual" }, + "traps": ["wrong-persona", "seed-data-thin"], + "source": ["examples/app-showcase/access-matrix.json (variant source — the expectation table)", "examples/app-showcase/src/security/permission-sets.ts", "ADR-0090 D1/D5", "packages/qa/dogfood/test/showcase-permission-zoo.dogfood.test.ts"], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new — CRUD × permission matrix grounded in access-matrix.json, per the deep-test contract", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "access-security.owd-sharing-matrix", + "title": "Sharing-model / OWD matrix: private, public_read, public_read_write, controlled_by_parent each enforce their declared baseline", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P0", + "surface": "api", + "personas": ["admin", "members A and B (baseline only)", "contributor members for the invoice/line and project cases", "ops (modifyAllRecords contrast on announcements)", "auditor (viewAllRecords contrast on lines)"], + "fixtures": { + "app": "showcase", + "requires": ["one seeded object per model, from the access-matrix sharingModel column: showcase_private_note (private), showcase_announcement (public_read), showcase_account + showcase_project (public_read_write), showcase_invoice_line under showcase_invoice (controlled_by_parent)", "contributor invoice RLS owner == current_user.email (permission-sets.ts invoice_own_rows) — invoices must be created with owner set to the creating contributor's email"] + }, + "steps": [ + "boot showcase isolated; create members A and B (baseline), two contributor members C1/C2 (grant showcase_contributor), an ops member, an auditor member", + "private: as A create a showcase_private_note; as B list, GET by id, PATCH it", + "public_read: as admin (or ops) create a showcase_announcement owned by someone other than B; as B GET list + by id, then PATCH it; as ops PATCH the same announcement (modifyAllRecords contrast)", + "public_read_write: as admin create a showcase_project; as C1 (allowEdit:true on projects, no project RLS) GET it and PATCH a non-FLS field (e.g. name suffix) — record-level write open by OWD; as A (member_default, allowEdit:false) attempt the same PATCH — the object-level bit must still gate", + "controlled_by_parent: as C1 POST /api/v1/data/showcase_invoice {\"name\": \"INV-C1-1\", \"owner\": \"\", \"status\": \"draft\"} then POST /api/v1/data/showcase_invoice_line {\"invoice\": , \"product\": , \"quantity\": 1}; as C2 list lines, GET C1's line by id, PATCH it", + "as C1: GET/PATCH their OWN line by id (the entitled side of ADR-0055 derivation)", + "as auditor: list showcase_invoice_line — viewAllRecords crosses the derived scope", + "capture per-model, per-persona verb outcomes" + ], + "acceptance": [ + { + "clause": "private: only the owner reads/writes — B's list excludes A's note, B's by-id GET and PATCH are non-2xx, A's own read/write 2xx", + "oracle": "api", + "verify": "the four traces; PATCH denial confirmed unchanged by admin re-read", + "evidence": "traces + re-read" + }, + { + "clause": "public_read: everyone reads, only owner writes — B GET 200 (list and by id) but B PATCH >=400 with the row unchanged", + "oracle": "api", + "verify": "B's GET/PATCH traces + admin re-read of the announcement", + "evidence": "traces + re-read" + }, + { + "clause": "public_read: modifyAllRecords crosses the owner-writes baseline — ops' PATCH of the same foreign announcement is 2xx and persists (the bypass matters exactly where the baseline stops)", + "oracle": "api", + "verify": "ops PATCH 2xx + re-read shows the change", + "evidence": "trace + re-read" + }, + { + "clause": "public_read_write: record-level write is open — C1 (holding allowEdit on showcase_project) PATCHes a project they do not own with 2xx; the object-level bit still gates: A's identical PATCH (member_default allowEdit:false) answers 403 PERMISSION_DENIED", + "oracle": "api", + "verify": "both PATCH traces; the OWD opens records, never verbs the set withholds", + "evidence": "both traces" + }, + { + "clause": "controlled_by_parent: line access derives from the master (ADR-0055) — C2 cannot list, read by id, or PATCH C1's line (C1's invoice is outside C2's owner-RLS read set); C1 reads and writes their own line by id", + "oracle": "api", + "verify": "C2's three denials (list excludes, by-id non-200, PATCH >=400 + unchanged) and C1's 2xx pair; no line-level rule is authored — derivation is the only mechanism in play", + "evidence": "all traces" + }, + { + "clause": "controlled_by_parent + VAMA: the auditor's viewAllRecords on showcase_invoice_line surfaces every line regardless of master ownership", + "oracle": "api", + "verify": "auditor list contains C1's line", + "evidence": "auditor listing" + }, + { + "clause": "all four model variants are exercised and each verdict is recorded per persona-verb — a model not driven leaves the item partial", + "oracle": "api", + "verify": "run record carries verdicts for private / public_read / public_read_write / controlled_by_parent", + "evidence": "run record" + } + ], + "negative": ["the public_read foreign PATCH must fail server-side even when the console hides the edit button — drive it as a forged direct request; a 2xx there is a FAIL (D10)", "run C2's line probes ALSO as C1 to prove the denial is derivation, not a broken line route (wrong-persona both sides)"], + "variants": ["private", "public_read", "public_read_write", "controlled_by_parent"], + "automated": { "kind": "dogfood", "ref": "packages/qa/dogfood/test/showcase-private-owd.dogfood.test.ts + showcase-public-read-owd.dogfood.test.ts + controlled-by-parent.dogfood.test.ts + showcase-invoice-cbp.dogfood.test.ts" }, + "traps": ["wrong-persona"], + "source": ["packages/spec/src/security/sharing.zod.ts (the four-model enum — variant source)", "examples/app-showcase/access-matrix.json (sharingModel column)", "ADR-0055", "authz-conformance.matrix.ts rows owd-private / owd-public-read / controlled-by-parent"], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new — OWD matrix over the four spec sharing models, per the deep-test contract", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "access-security.fls-mask-and-strip", + "title": "Field-level security: editable:false strips/denies writes API-side and renders read-only in the UI; masked-read half needs an authored readable:false grant", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["contributor (the FLS-restricted persona: showcase_project.budget/spent/budget_remaining readable:true, editable:false)", "admin (the unrestricted contrast)"], + "fixtures": { + "app": "showcase", + "requires": ["showcase_contributor.fields FLS on showcase_project budget figures (permission-sets.ts — keys are . qualified; bare keys silently enforce nothing, the compile-time lint security-fls-unqualified-key guards that)", "a seeded showcase_project row with a non-null budget"], + "knownGaps": ["stock showcase authors NO readable:false FLS grant, so the read-MASKING half (field absent/nulled on GET, plugin-security/src/field-masker.ts) has no stock fixture; to run it, author a scratch permission set carrying readable:false on a showcase_project field and grant it to a fresh member — if the run cannot author one through a supported surface, record that half blocked(fixture) rather than ticking on the write half alone"] + }, + "steps": [ + "boot showcase with the console; contributor member (grant showcase_contributor); pick a seeded showcase_project and record its budget via a system-context read", + "as contributor over the API: PATCH /api/v1/data/showcase_project/ {\"name\": \" (edited)\"} — an editable field, then PATCH {\"budget\": 999999999} — the FLS-locked field", + "re-read the project system-side; compare budget before/after", + "as admin: PATCH the same budget field to a new value and re-read (the entitled side)", + "in the console as the contributor: open the project's detail/edit form; screenshot; inspect the budget/spent/budget_remaining controls' state", + "read-mask half (see knownGaps): grant a scratch readable:false set to a fresh member, then GET the project as that member and inspect whether the masked field is absent/nulled in the API body AND blank in the UI render", + "as admin GET the same row — the unmasked contrast" + ], + "acceptance": [ + { + "clause": "editable field write succeeds for the contributor (the FLS lock is per-field, not per-object)", + "oracle": "api", + "verify": "name PATCH 2xx and re-reads changed", + "evidence": "trace + re-read" + }, + { + "clause": "editable:false field write is refused/stripped for the contributor and the stored value is unchanged (the permission-zoo pinned behavior: status >= 400 and budget identical)", + "oracle": "api", + "verify": "budget PATCH answers >=400 (or a documented strip) AND the system-context re-read equals the pre-write budget — the value oracle decides, not the status alone", + "evidence": "trace + before/after reads" + }, + { + "clause": "the SAME write succeeds for admin — the lock keys on the caller's FLS, not on the field (both sides)", + "oracle": "api", + "verify": "admin budget PATCH 2xx + persisted re-read", + "evidence": "trace + re-read" + }, + { + "clause": "the UI renders the FLS state faithfully for the contributor: budget figures visible (readable:true) but not editable — input disabled/read-only on the edit form", + "oracle": "dom", + "verify": "after a screenshot confirms the form rendered, assert the disabled/read-only state of the three budget controls", + "evidence": "screenshot + DOM read" + }, + { + "clause": "read masking (given the scratch readable:false grant): the masked field is absent or nulled in the member's API read AND blank in their UI render, while admin's read carries the value — API and UI agree, both personas", + "oracle": "api", + "verify": "member GET body lacks/nulls the field; admin GET carries it; UI screenshots per persona match their API bodies", + "evidence": "both GET bodies + screenshots" + } + ], + "negative": ["a UI-only lock is a FAIL: if the form disables the control but the direct PATCH mutates budget, record a server-guard gap (the API clause is the oracle, the DOM clause is corroboration only)"], + "traps": ["wrong-persona", "stale-console-bundle", "hydration-race"], + "automated": { "kind": "dogfood", "ref": "packages/qa/dogfood/test/showcase-permission-zoo.dogfood.test.ts (FLS budget case — write half only)" }, + "source": ["examples/app-showcase/src/security/permission-sets.ts (contributor FLS)", "packages/plugins/plugin-security/src/field-masker.ts (read-mask enforcement site)", "ADR-0090 D10 (mask intersection)"], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new — FLS item per the deep-test contract; read-mask half carries an explicit fixture gap instead of an ungrounded step", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "access-security.scope-depth-asymmetry", + "title": "Scope depth (readScope/writeScope): org-wide read with own-only write, per persona — depth widens along geometry, never bypasses", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": ["plain member M (creates the foreign probe inquiries via member_default create:true)", "manager (readScope org / writeScope own on showcase_inquiry)", "executive (readScope org, no write, on showcase_inquiry + showcase_private_note)", "ops (readScope org / writeScope org on showcase_inquiry)"], + "fixtures": { + "app": "showcase", + "requires": ["showcase_inquiry is OWD private with create granted to the baseline — depth only matters where the baseline stops (permission-sets.ts docblock)", "personas staffed by granting the sets (sys_user_permission_set) or assigning the bound positions manager/exec/ops (bind-position-sets.ts wires position → set at boot)"] + }, + "steps": [ + "boot showcase isolated; create member M plus manager/executive/ops personas; as M: POST /api/v1/data/showcase_inquiry {\"subject\": \"M probe\", ...minimal valid payload}; as the manager: POST their OWN inquiry", + "as the manager: GET /api/v1/data/showcase_inquiry (list) — M's inquiry must appear (readScope org on a private object)", + "as the manager: PATCH M's inquiry (foreign write, writeScope own) and PATCH their own inquiry", + "as the executive: GET the inquiry list and M's inquiry by id; then attempt PATCH (no edit bit at all); also GET /api/v1/data/showcase_private_note list (readScope org over private notes)", + "as ops: PATCH M's inquiry (writeScope org)", + "as M: GET the manager's inquiry by id — the baseline member must NOT get org-wide read (the contrast that proves depth did the widening)", + "capture all traces + admin re-reads after every denied write" + ], + "acceptance": [ + { + "clause": "manager reads org-wide on the private object: M's inquiry present in the manager's list and readable by id", + "oracle": "api", + "verify": "GET list contains 'M probe'; by-id GET 200", + "evidence": "traces" + }, + { + "clause": "manager's write stays own-scoped (the read/write ASYMMETRY): PATCH on M's inquiry >=400 with the row unchanged; PATCH on the manager's own inquiry 2xx", + "oracle": "api", + "verify": "both PATCH traces + admin re-read of M's inquiry", + "evidence": "traces + re-read" + }, + { + "clause": "executive reads org-wide (inquiries AND private notes) but cannot write at all — every PATCH >=400, rows unchanged", + "oracle": "api", + "verify": "list/by-id GETs 200 with foreign rows present; PATCH >=400 + unchanged re-read", + "evidence": "traces + re-read" + }, + { + "clause": "ops writes org-wide: PATCH on M's inquiry 2xx and persisted (the entitled side of the exact guard that denied the manager)", + "oracle": "api", + "verify": "ops PATCH 2xx + re-read shows the change", + "evidence": "trace + re-read" + }, + { + "clause": "the baseline member has NO org-wide read: M cannot read the manager's inquiry by id and M's list holds only M's own — depth was the widener, not the object or route", + "oracle": "api", + "verify": "M's by-id GET non-200; M's list excludes the manager's inquiry", + "evidence": "traces" + }, + { + "clause": "each persona variant is driven and recorded (manager / executive / ops read+write outcomes) — the asymmetry table in the run record matches the readScope/writeScope columns of access-matrix.json", + "oracle": "api", + "verify": "diff the recorded outcomes against the access-matrix.json readScope/writeScope annotations for showcase_manager/showcase_executive/showcase_ops", + "evidence": "the outcome table" + } + ], + "negative": ["a manager PATCH of M's inquiry that answers 2xx is a FAIL even if some UI would have hidden the row — writeScope own must be enforced on the forged direct request; and the same PATCH as ops must SUCCEED, or the finding is a broken route rather than a working guard (wrong-persona, both sides)"], + "variants": ["manager: read org / write own", "executive: read org / write none", "ops: read org / write org"], + "automated": { "kind": "dogfood", "ref": "packages/qa/dogfood/test/showcase-scope-depth.dogfood.test.ts (+ showcase-scope-depth-write, showcase-scope-depth-fallback)" }, + "traps": ["wrong-persona"], + "source": ["examples/app-showcase/src/security/permission-sets.ts (ADR-0057 D1 dials)", "examples/app-showcase/access-matrix.json (readScope/writeScope columns — variant source)", "ADR-0057 D1"], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new — scope-depth read/write asymmetry matrix, per the deep-test contract", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "access-security.anonymous-deny-surfaces", + "title": "Anonymous requests to every mounted API family answer 401 UNAUTHENTICATED — uniformly, before any resource resolution", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P0", + "surface": "api", + "personas": ["anonymous (no Authorization header)", "an authenticated member (the unaffected contrast)"], + "fixtures": { + "app": "showcase", + "requires": ["stock secure default — boot with NO requireAuth override, exactly as the dogfood pin does; the platform default is what a fresh production deployment gets"] + }, + "steps": [ + "boot showcase on the platform-default auth posture; do NOT sign in for the probe half", + "fire unauthenticated requests, one per mounted surface family: GET /api/v1/data/showcase_private_note (data), GET /api/v1/meta (metadata), POST /api/v1/actions/showcase_task/showcase_mark_done/anon-probe-id (dispatcher actions — deliberately a NONEXISTENT record id), GET /api/v1/automation (dispatcher automation), POST /api/v1/batch {\"operations\": []} (batch), GET /api/v1/security/explain (security-explain)", + "capture status + full body per surface", + "classify each 401 body into exactly ONE of the two declared envelope families (rest-flat vs dispatcher-wrapper, #5632) — no tolerant cross-family reads", + "sign in as a member and repeat the data + meta reads to prove the gate keys on anonymity, not on the routes", + "fetch a declared-public route (rest-route-ledger disposition 'public', forms family) anonymously — the gate must not over-deny it" + ], + "acceptance": [ + { + "clause": "every probed surface answers HTTP 401 with error code UNAUTHENTICATED (ANONYMOUS_DENY_STATUS/ANONYMOUS_DENY_CODE from @objectstack/core) — no surface differs", + "oracle": "api", + "verify": "all captured statuses == 401 and each body's code == 'UNAUTHENTICATED'", + "evidence": "the per-surface traces" + }, + { + "clause": "denial happens BEFORE resource resolution: the actions probe with a nonexistent record id still answers 401 (never 404) — an anonymous caller must not learn the route's shape (#5519: the gate is the handler's first statement)", + "oracle": "api", + "verify": "POST /api/v1/actions/showcase_task/showcase_mark_done/anon-probe-id → 401, not 404/400", + "evidence": "trace" + }, + { + "clause": "every 401 body classifies into exactly one of the two declared envelope families — a third dialect (hybrid/re-nested body) is a FAIL even though the status is right (#5632)", + "oracle": "api", + "verify": "mutually exclusive family predicates as in showcase-anonymous-deny-surfaces.dogfood.test.ts: rest-flat (top-level error string, no success flag) for /data /meta /batch /security, dispatcher-wrapper for /actions /automation", + "evidence": "classified bodies" + }, + { + "clause": "an authenticated member is unaffected: the same data/meta reads answer 200 for a signed-in baseline member", + "oracle": "api", + "verify": "member GET /api/v1/data/showcase_private_note and GET /api/v1/meta → 200", + "evidence": "member traces" + }, + { + "clause": "declared-public surfaces stay public: a rest-route-ledger 'public' route (anonymous forms) answers without the 401 — the deny must gate on the route's declared posture, not blanket the server (both sides of the gate)", + "oracle": "api", + "verify": "anonymous fetch of a forms-family public route answers non-401", + "evidence": "trace" + }, + { + "clause": "every surface variant is probed and recorded; a family not driven (e.g. only /data checked) leaves the item partial — the #5519 lesson is exactly that sibling surfaces drifted while /data looked fine", + "oracle": "api", + "verify": "run record carries one verdict per variant below", + "evidence": "run record" } ], - "source": ["#3358 §5"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }] + "negative": ["the destructive automation case must be denied too: anonymous DELETE /api/v1/automation/showcase_reassign_wizard answers 401 and the flow remains registered afterwards (verify by an authed GET /api/v1/automation listing it) — a 200 {deleted:true} is the exact #5519 regression"], + "variants": ["data (/api/v1/data)", "metadata (/api/v1/meta)", "actions (/api/v1/actions — dispatcher-mounted)", "automation (/api/v1/automation — dispatcher-mounted)", "batch (/api/v1/batch)", "security-explain (/api/v1/security/explain)"], + "automated": { "kind": "dogfood", "ref": "packages/qa/dogfood/test/showcase-anonymous-deny-surfaces.dogfood.test.ts" }, + "traps": ["dispatcher-vs-hono-route"], + "source": ["release-15.1 plan §A8", "#2567", "#5519/#5569/#5570", "#5632", "authz-conformance.matrix.ts anonymous-deny rows (covers keys)", "packages/core/src/security/anonymous-deny.ts"], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new — auth-boundary sweep over every mounted family, grounded in the #2567/#5519 conformance rows and their dogfood pin", "ref": "claude/platform-test-checklist-ocwugl" } + ] } ] } diff --git a/docs/qa/platform-checklist/areas/automation.json b/docs/qa/platform-checklist/areas/automation.json index 6d7b9e0860..1e687e318d 100644 --- a/docs/qa/platform-checklist/areas/automation.json +++ b/docs/qa/platform-checklist/areas/automation.json @@ -8,64 +8,149 @@ "title": "Flow Runs render loop/region iterations as a nested execution tree", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", - "surface": "browser", + "surface": "mixed", "personas": ["admin"], - "fixtures": { "app": "showcase", "requires": ["a loop flow runnable on demand (e.g. showcase_batch_reminders with a small list input)"] }, + "fixtures": { + "app": "showcase", + "requires": ["showcase_batch_reminders (examples/app-showcase/src/automation/flows/index.ts BatchRemindersFlow) — an autolaunched loop flow with a `tasks` list input, runnable on demand via the trigger route"] + }, "steps": [ - "run the loop flow with a 3-item input", - "open the FLOW DESIGNER's Runs panel for that run (not the developer Flow Runs page)" + "boot showcase isolated (dogfood §0); sign in as the dev admin", + "POST /api/v1/automation/showcase_batch_reminders/trigger with body {\"params\": {\"tasks\": [t1, t2, t3]}} — three task-shaped objects each carrying id/title/owner so the loop body's notify node can interpolate {task.title}/{task.owner}", + "GET /api/v1/automation/showcase_batch_reminders/runs, take the newest run id, then GET /api/v1/automation/showcase_batch_reminders/runs/:runId for the full step log", + "record every step's nodeId, nodeType, status, parentNodeId, iteration, regionKind (ExecutionStepLogSchema #1505 region tags)", + "open the flow in the Studio flow-designer (metadata-admin) and its Runs panel (FlowRunsPanel) — NOT the developer Flow Runs page — and expand the newest run", + "screenshot the expanded step tree showing the per-iteration children under the loop node", + "contrast run: trigger again with {\"params\": {\"tasks\": []}} and capture the loop step of that run" ], "acceptance": [ { - "clause": "the run's steps carry parent/iteration/region tags and the designer Runs panel renders them as a nested tree (per-iteration children under the loop node), not a flat list", + "clause": "the 3-item run completes (status=completed) and the loop body's send_reminder node executed exactly 3 times", + "oracle": "api", + "verify": "GET the run detail: status=completed; count steps with nodeId=send_reminder == 3", + "evidence": "run-detail API read" + }, + { + "clause": "every body step carries the region tags: parentNodeId=loop_tasks, iteration in 0..2, regionKind='loop-body'", + "oracle": "api", + "verify": "assert the three send_reminder steps each carry {parentNodeId:'loop_tasks', iteration: 0|1|2, regionKind:'loop-body'} and no two share an iteration; top-level steps (start/loop_tasks/end) carry NO parentNodeId", + "evidence": "step-log excerpt with the tags" + }, + { + "clause": "the designer Runs panel renders the iterations as a nested tree (per-iteration children folded under the loop node, labeled 1-based), not a flat list", + "oracle": "screenshot", + "verify": "screenshot of the expanded run shows send_reminder rows indented under 'For each task' grouped by iteration — matches FlowRunsPanel buildStepTree (#1505)", + "evidence": "Runs panel screenshot" + }, + { + "clause": "the rendered tree agrees with the API: same step count and same parent/iteration grouping", "oracle": "api", - "verify": "the run detail API shows parent=loop node, iter=0..2, region=loop-body per body step; the panel screenshot shows the ITERATION-nested rendering", - "evidence": "run-detail API read + Runs panel screenshot" + "verify": "cross-check the screenshot's grouping against the recorded parentNodeId/iteration tags — a tree the API tags cannot reconstruct is a rendering invention", + "evidence": "screenshot + step-log side by side" + }, + { + "clause": "empty-collection contrast: the 0-item run still completes and its loop step succeeds with zero body children", + "oracle": "api", + "verify": "run 2 detail: status=completed, no steps with parentNodeId=loop_tasks — 'nothing to iterate' is a success with an empty region, not a failure", + "evidence": "second run-detail read" } ], - "traps": ["wrong-panel"], - "source": ["#3358 §2 — 'the developer Flow Runs page renders steps flat; looking there alone reads as a miss'"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358, encoding its wrong-panel lesson as a trap", "ref": "#3358" }] + "negative": [ + "a flat rendering in which all steps show success is a FAIL of the nesting clause — every step passing is exactly what makes the missing tree easy to tick past; the developer Flow Runs page renders steps flat and must never be cited as this item's oracle" + ], + "traps": ["wrong-panel", "hydration-race"], + "source": [ + "#3358 §2 — 'the developer Flow Runs page renders steps flat; looking there alone reads as a miss'", + "packages/spec/src/automation/execution.zod.ts (ExecutionStepLogSchema parentNodeId/iteration/regionKind)", + "objectui packages/app-shell/src/views/metadata-admin/previews/FlowRunsPanel.tsx (#1505 buildStepTree)", + "examples/app-showcase/src/automation/flows/index.ts (BatchRemindersFlow)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358, encoding its wrong-panel lesson as a trap", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "automation.time-relative-trigger", "title": "Time-relative flow triggers author first-class and fire per matching record", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", "surface": "mixed", "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": ["showcase_task_due_reminder (type schedule, start config.timeRelative {object: showcase_task, dateField: due_date, offsetDays: [3,1], filter: {status: {$ne: 'done'}}}, runAs system)"], + "knownGaps": ["the stock sweep cadence is the timeRelative default — daily 08:00 UTC — so an in-session fire needs either a cadence override (config.schedule cron/interval on a writable copy of the flow) or a manually provoked sweep; the run record must state which method was used"] + }, "steps": [ - "author a flow start node with timeRelative (offsetDays or withinDays) in the designer", - "confirm the boot banner binds it; where feasible, stage a record whose date matches and confirm the daily sweep fires" + "boot showcase isolated (dogfood §0); sign in as the dev admin", + "open showcase_task_due_reminder in the Studio flow-designer; open the start node's panel", + "screenshot the first-class timeRelative editor (sweep object / date field / offset days as a numberList editor — not raw JSON)", + "read the server boot log: the Flows banner must count showcase_task_due_reminder bound as a time_relative trigger (requires: ['triggers'] — engine warns 'no time_relative trigger is registered' otherwise)", + "POST /api/v1/data/showcase_task: one task with due_date exactly 3 days from today and status='todo' (matches offsetDays AND the filter), one with due_date 10 days out (no offset match), one due in 3 days but status='done' (excluded by the filter)", + "fire the sweep (per the knownGaps method) and record how it was fired", + "GET /api/v1/automation/showcase_task_due_reminder/runs — locate the run(s) produced by the sweep", + "GET the matching run's detail: trigger kind, the interpolated notify step, and the recipient", + "author-negative: in a scratch/writable package author a flow whose timeRelative sets BOTH offsetDays and withinDays, and build/validate it" ], "acceptance": [ { - "clause": "the designer renders the first-class timeRelative panel (sweep object / date field / offset days as a numberList editor, not raw JSON)", + "clause": "the designer renders the dedicated timeRelative panel (sweep object / date field / offset days), not a raw-JSON fallback", "oracle": "screenshot", - "verify": "designer panel screenshot shows the dedicated fields", + "verify": "designer panel screenshot shows the named fields populated from the flow source", "evidence": "screenshot" }, { - "clause": "the flow binds at boot and a matching record produces a run on the sweep", + "clause": "the flow binds at boot as a time_relative trigger", "oracle": "log", - "verify": "Flows banner counts it bound as time_relative; a staged matching record yields a run row after the sweep", - "evidence": "banner line + run list read" + "verify": "boot log counts it bound as time_relative (trigger-schedule time-relative-trigger.ts registers type 'time_relative'); no 'no time_relative trigger is registered' warning", + "evidence": "boot log excerpt" + }, + { + "clause": "the sweep launches the flow once for the matching record, with that record on the flow context, and the run row records the time_relative trigger kind", + "oracle": "api", + "verify": "exactly one run whose trigger records time_relative (engine stamps triggerType 'time_relative' — service-automation engine.ts) and whose notify step interpolated the staged task's {record.title}", + "evidence": "runs list + run-detail reads" + }, + { + "clause": "non-matching records produce NO run: the 10-days-out task (outside offsetDays) and the done task (excluded by filter) are both skipped", + "oracle": "api", + "verify": "runs list contains no run whose context record is either non-matching task — assert the absence, not just the presence", + "evidence": "runs list read after the sweep" + }, + { + "clause": "declaring both windowing modes is rejected at authoring: timeRelative requires exactly ONE of withinDays / offsetDays", + "oracle": "build", + "verify": "the both-modes scratch flow fails build/validate with a located error naming the constraint (packages/spec/src/automation/time-relative-trigger.zod.ts)", + "evidence": "build/validate output" } ], - "traps": ["seed-data-thin"], - "source": ["#3358 §2"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }] + "negative": [ + "a sweep that also fires for the filter-excluded or out-of-window record is a FAIL even though the matching record fired correctly", + "a both-windowing-modes flow accepted silently is a FAIL — the schema declares exactly one mode" + ], + "traps": ["seed-data-thin", "stale-dist"], + "source": [ + "#3358 §2", + "packages/spec/src/automation/time-relative-trigger.zod.ts", + "examples/app-showcase/src/automation/flows/index.ts (TaskDueReminderFlow, #1874)", + "packages/triggers/trigger-schedule/src/time-relative-trigger.ts" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "automation.rollup-summary-filter", "title": "Filtered roll-up summaries recompute only through their own filter", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "mixed", "personas": ["admin"], @@ -75,27 +160,523 @@ "knownGaps": ["the visual filter-editor half needs a summary field in a WRITABLE package — the showcase ships read-only, so Studio editing is not reachable on stock fixtures (#3358)"] }, "steps": [ - "flip one child row's filtered dimension over REST (e.g. status approved→rejected)", - "re-read every roll-up on the parent and independently recompute each from the child rows" + "boot showcase isolated (dogfood §0); sign in as the dev admin", + "GET /api/v1/data/showcase_expense_report for EXP-2001 and record all six roll-ups: total_amount (unfiltered SUM), approved_amount (SUM where status=approved), reimbursable_amount (SUM where billable=true), line_count (unfiltered COUNT), rejected_count (COUNT where status=rejected), over_limit_count (COUNT where amount >= 500)", + "GET the child showcase_expense_line rows for that report and independently recompute each roll-up from them", + "PATCH one line's status approved→rejected over /api/v1/data/showcase_expense_line/:id; re-read the parent and recompute again", + "PATCH a different line's billable true→false; re-read the parent", + "DELETE one line; re-read the parent — a delete must move every roll-up whose filter matched the deleted row, including the unfiltered total", + "(editor half, blocked) on a writable package: edit a summary field's child-row filter in Studio's visual filter editor, save, and re-read the persisted metadata" ], "acceptance": [ { - "clause": "only the roll-ups whose filter the edit touched move; every value matches an independent recomputation from the child rows", + "clause": "baseline: every roll-up equals an independent recomputation from the child rows before any edit", + "oracle": "api", + "verify": "value table: six roll-ups vs six hand-recomputed aggregates over the fetched lines — all equal", + "evidence": "parent + child reads with the recomputation table" + }, + { + "clause": "after the status flip, only the status-filtered roll-ups move (approved_amount down, rejected_count up) and each lands exactly on the recomputed value", + "oracle": "api", + "verify": "before/after parent reads: approved_amount and rejected_count equal recomputation; total_amount, line_count, reimbursable_amount, over_limit_count byte-identical to before (the part a naive recompute-everything gets wrong)", + "evidence": "before/after value table" + }, + { + "clause": "after the billable flip, only reimbursable_amount moves", + "oracle": "api", + "verify": "before/after reads: reimbursable_amount equals recomputation; all five other roll-ups byte-identical", + "evidence": "before/after value table" + }, + { + "clause": "a child delete recomputes every roll-up the deleted row participated in, including the unfiltered total and count", "oracle": "api", - "verify": "before/after parent reads: touched roll-ups change to the recomputed value; unfiltered and untouched-filter roll-ups stay exactly put (the part a naive recompute-everything gets wrong)", - "evidence": "before/after value table with the independent recomputation" + "verify": "after DELETE: total_amount and line_count drop by the deleted row's contribution; filtered roll-ups whose predicate matched it drop too; roll-ups whose predicate did not match are unchanged", + "evidence": "before/after value table" }, { "clause": "the child-row filter is settable via the visual editor on a writable package", "oracle": "screenshot", - "verify": "edit a summary field's filter in Studio on a writable package and confirm the persisted metadata", + "verify": "edit a summary field's filter in Studio on a writable package and confirm the persisted metadata via the meta API", "evidence": "editor screenshot + saved metadata read" } ], + "negative": [ + "an untouched-filter roll-up that moves on an edit outside its filter (e.g. rejected_count changing on the billable flip) is a FAIL even if every touched value is right" + ], "blocked": { "by": "fixture", "ref": "#3358 (editor half needs a writable-package summary field fixture)" }, "traps": ["seed-data-thin"], - "source": ["#3358 §2 (recompute half proven; editor half explicitly left unticked)"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; clause 1 runnable today, clause 2 carries the fixture blocker", "ref": "#3358" }] + "source": [ + "#3358 §2 (recompute half proven; editor half explicitly left unticked)", + "examples/app-showcase/src/data/objects/expense-report.object.ts (the six roll-up shapes and their filters)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; clause 1 runnable today, clause 2 carries the fixture blocker", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "automation.flow-node-type-matrix", + "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": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "the showcase flow-per-node-type map (examples/app-showcase/src/automation/flows/): create_record=showcase_inbound_task_webhook · update_record+screen=showcase_reassign_wizard · get_record+decision+delete_record=showcase_inquiry_purge · script+notify=showcase_task_completed · wait=showcase_task_follow_up · subflow=showcase_task_done_notify_owner→showcase_notify_owner · map=showcase_release_signoff · connector_action=showcase_task_completed_rest_ping · loop=showcase_batch_reminders · parallel+http=showcase_fan_out_notify · try_catch=showcase_resilient_sync · assignment=showcase_closure_signoff · approval=showcase_expense_signoff · approval_revise=showcase_budget_approval (wait_revision)" + ], + "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" + ] + }, + "variants": [ + "start", "end", "decision", "assignment", "loop", + "create_record", "update_record", "delete_record", "get_record", + "http", "notify", "script", "screen", "wait", "subflow", "map", + "connector_action", "parallel", "try_catch", "approval", "approval_revise" + ], + "steps": [ + "boot showcase isolated (dogfood §0); sign in as the dev admin", + "GET /api/v1/automation/actions (the designer palette feed) and record the descriptor list; open the Studio flow-designer palette and screenshot it", + "record_change chain: POST a showcase_task then PATCH status→done — fires showcase_task_completed (script+notify), showcase_fan_out_notify (parallel+http), showcase_resilient_sync (try_catch), showcase_task_done_notify_owner (subflow); the create also fires showcase_task_follow_up (wait) and showcase_declarative_connector_ping / showcase_mcp_connector_echo (connector_action)", + "on-demand chain: POST /api/v1/automation//trigger for showcase_batch_reminders {params:{tasks:[…]}} (loop), showcase_inquiry_purge (get_record+decision+delete_record — seed at least one closed showcase_inquiry first), showcase_release_signoff {params:{items:[…]}} (map)", + "browser chain: run the Tasks row action showcase_bulk_reassign → showcase_reassign_wizard (screen + update_record)", + "approval chain: PATCH a showcase_expense_report to submitted (approval via showcase_expense_signoff); PATCH a showcase_project budget above 100000 to open showcase_budget_approval, then POST /api/v1/approvals/requests/:id/revise so the run parks at the approval_revise node", + "webhook chain: POST /api/v1/automation/hooks/showcase_inbound_task_webhook/intake with a valid x-objectstack-signature HMAC (secret 'showcase-webhook-secret') to exercise create_record", + "for EACH variant: GET the owning flow's runs (GET /api/v1/automation//runs + /runs/:runId) and locate a step whose nodeType equals the variant; record run id, step status, and — for suspending types — the paused-then-resumed transition", + "screenshot the Runs panel of one composite run (showcase_project_escalation or showcase_fan_out_notify) showing the container-nested steps" + ], + "acceptance": [ + { + "clause": "the designer palette (fed by GET /api/v1/automation/actions) offers every variant in the matrix as an authorable node", + "oracle": "api", + "verify": "the actions descriptor list contains every variant id; the palette screenshot (taken after render) shows them offered", + "evidence": "actions API read + palette screenshot" + }, + { + "clause": "per-variant: each node type appears as an executed step (status success, or the documented pause for suspending types) in at least one captured run — every variant individually verified and recorded", + "oracle": "api", + "verify": "for each of the 21 variants cite the run id + step whose nodeType matches; a variant with no located step is UNPROVEN for that variant, not a partial pass of the matrix", + "evidence": "per-variant table of run id / step / status" + }, + { + "clause": "suspending types (screen, wait, approval, approval_revise — and subflow/map when the child pauses) park the run status=paused and resume to completed", + "oracle": "api", + "verify": "run detail shows status paused while parked and completed after the screen resume / timer elapse / approval decision / resubmit (ExecutionStatus vocabulary: pending|running|paused|completed|failed|cancelled|timed_out|retrying)", + "evidence": "before/after run-detail reads per suspending type" + }, + { + "clause": "every step in every captured run log names its nodeType, so the matrix is auditable from the run API alone", + "oracle": "api", + "verify": "no step in the captured runs has an empty/missing nodeType", + "evidence": "step-log excerpts" + }, + { + "clause": "container-region body steps (loop / parallel / try_catch) carry their region tags in the same runs", + "oracle": "api", + "verify": "spot-check one body step per container kind for parentNodeId + regionKind (deep coverage lives in automation.flow-run-step-nesting)", + "evidence": "tagged step excerpts" + } + ], + "negative": [ + "registering a flow with an unregistered node type (e.g. type 'bogus_node') must be REFUSED at registerFlow / POST /api/v1/automation — the type is validated against the live action registry (ADR-0018), and silent acceptance of an inert node is the #1887 failure shape" + ], + "traps": ["wrong-panel", "seed-data-thin", "automation-input"], + "automated": { "kind": "e2e", "ref": "packages/qa/dogfood/test/flow-node.dogfood.test.ts" }, + "source": [ + "packages/spec/src/automation/flow.zod.ts (FlowNodeAction — the built-in seed set; type validated at registerFlow, not by a closed enum)", + "packages/spec/src/automation/control-flow.zod.ts (LOOP_NODE_TYPE / PARALLEL_NODE_TYPE / TRY_CATCH_NODE_TYPE, ADR-0031)", + "packages/spec/src/automation/approval.zod.ts (APPROVAL_NODE_TYPE, APPROVAL_REVISE_NODE_TYPE)", + "examples/app-showcase/src/coverage.ts (flowNodeTypes + FLOW_NODE_WAIVERS)", + "examples/app-showcase/src/automation/flows/index.ts", + "packages/runtime/src/route-ledger.ts (GET /automation/actions, POST /automation/:name/trigger, GET /automation/:name/runs)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — node-type matrix derived from FlowNodeAction + ADR-0031 containers + plugin-approvals registry types, mapped onto the seeded showcase flows", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "automation.trigger-type-matrix", + "title": "Every flow trigger type fires and its run row records the trigger kind", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "one seeded flow per trigger kind: record_change=showcase_urgent_task_alert (record-after-write, the only type:'record_change' flow) · autolaunched=showcase_batch_reminders (trigger route) · schedule=showcase_scheduled_digest (60s interval) · screen=showcase_reassign_wizard (Tasks row action showcase_bulk_reassign) · api=showcase_inbound_task_webhook (HMAC intake hook) · time_relative=showcase_task_due_reminder (schedule flow hosting config.timeRelative)" + ] + }, + "variants": ["record_change", "autolaunched", "schedule", "screen", "api", "time_relative"], + "steps": [ + "boot showcase isolated (dogfood §0); sign in as the dev admin", + "record_change (create leg): POST /api/v1/data/showcase_task with priority='urgent' — showcase_urgent_task_alert fires via record-after-write with previous == null (#3427)", + "record_change (update leg): POST a normal-priority task, then PATCH priority→'urgent'; also PATCH a task status→'done' and confirm a further save WITHOUT a transition fires nothing", + "api: POST /api/v1/automation/hooks/showcase_inbound_task_webhook/intake with JSON {title, assignee, project} and header x-objectstack-signature: sha256= — expect a 202 ACK; then find the created showcase_task", + "schedule: wait up to ~60s for showcase_scheduled_digest (interval 60000ms) to tick; wait a second interval for a second tick", + "screen: from /apps/showcase_app/showcase_task run the row action showcase_bulk_reassign, fill the dialog, submit", + "autolaunched: POST /api/v1/automation/showcase_batch_reminders/trigger {params:{tasks:[…]}}", + "time_relative: execute per automation.time-relative-trigger; capture only its run row here for the trigger-kind tally", + "for each variant, GET /api/v1/automation//runs and record the newest run's trigger block (ExecutionLogSchema.trigger)" + ], + "acceptance": [ + { + "clause": "per-variant: each trigger kind produces a run whose run row records that trigger kind — all six individually verified", + "oracle": "api", + "verify": "for each variant cite the run id and its trigger.type (record_change runs also carry the object + recordId of the mutation); a variant with no run row is UNPROVEN, whatever the UI showed", + "evidence": "per-variant table of run id + trigger block" + }, + { + "clause": "record-after-write discriminates its legs: the created-urgent task and the escalated task each produce exactly one run; the no-transition save produces none", + "oracle": "api", + "verify": "runs list for showcase_urgent_task_alert: two runs (one per leg), and NO run whose trigger recordId is the no-transition save — assert the absence", + "evidence": "runs list + the three data mutations" + }, + { + "clause": "the webhook intake ACKs 202 and the flow reads the JSON payload as its record", + "oracle": "api", + "verify": "intake response is 202; the created showcase_task's title/assignee/project equal the POSTed body ({record.*} interpolation from the webhook payload)", + "evidence": "intake request/response + task read" + }, + { + "clause": "the schedule trigger fires repeatedly on its interval without manual help", + "oracle": "api", + "verify": "two ticks ⇒ two showcase_scheduled_digest run rows (and two inbox rows for admin@objectos.ai), timestamps ~60s apart", + "evidence": "runs list with timestamps" + }, + { + "clause": "the screen flow's run pauses at its screen node and the resume completes it", + "oracle": "network", + "verify": "capture the trigger POST and the /runs/:runId/resume POST; run detail shows paused→completed", + "evidence": "network trace + run reads" + }, + { + "clause": "build ratchet: every Flow.type enum value is classified with a live runtime in the trigger-conformance ledger", + "oracle": "test", + "verify": "run packages/qa/dogfood/test/flow-trigger-conformance.test.ts — it rediscovers the enum from flow.zod.ts and fails on any unclassified type", + "evidence": "test output" + } + ], + "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)", + "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": ["seed-data-thin", "dispatcher-vs-hono-route"], + "automated": { "kind": "test", "ref": "packages/qa/dogfood/test/flow-trigger-conformance.test.ts" }, + "source": [ + "packages/spec/src/automation/flow.zod.ts (Flow.type enum: autolaunched|record_change|schedule|screen|api)", + "packages/qa/dogfood/test/flow-trigger-conformance.ledger.ts (one enforced row per type, each with its runtime + proof)", + "packages/triggers/trigger-api/src/plugin.ts (HOOKS_PATH /api/v1/automation/hooks/:flowName/:hookId)", + "examples/app-showcase/src/automation/flows/index.ts (UrgentTaskAlertFlow #3427, ScheduledDigestFlow, InboundTaskWebhookFlow)", + "packages/spec/src/automation/execution.zod.ts (ExecutionLogSchema.trigger)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — trigger matrix derived from the Flow.type enum and its ADR-0060 D5 conformance ledger; time_relative added as the declarative sweep the schedule runtime hosts", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "automation.flow-error-handling", + "title": "A failing node inside try_catch is handled (catch region, $error binding); outside it fails the run loudly", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_resilient_sync — try region POSTs https://api.example.com/v1/tasks (unroutable from the isolated env, so the failure is deterministic); retry {maxRetries:3, backoffMs:1000, backoffMultiplier:2, maxRetryDelayMs:10000}; catch region writes sync_status/sync_error onto the task with the caught $error", + "a writable/scratch package to author the UNPROTECTED probe flow into (the showcase itself ships read-only)" + ] + }, + "steps": [ + "boot showcase isolated (dogfood §0); sign in as the dev admin", + "handled case: POST a showcase_task and PATCH status→'done' — showcase_resilient_sync runs, its try-region http node fails against the unroutable host, retries, then the catch region runs", + "GET /api/v1/automation/showcase_resilient_sync/runs/:runId — record overall status, the try step's status/error/regionKind, the catch step's status/regionKind, and the run duration", + "re-read the task over /api/v1/data/showcase_task/:id — sync_status and sync_error", + "unhandled case: POST /api/v1/automation a minimal autolaunched probe flow (start → http POST to the same unroutable URL, NOT wrapped in try_catch → end) in the scratch package; trigger it via POST /api/v1/automation//trigger", + "GET the probe run: overall status and the run-level error", + "open the designer Runs panel for both runs; screenshot the failed step marked with its error and the catch-body nesting", + "capture the server log lines for the probe run's failure" + ], + "acceptance": [ + { + "clause": "handled: the run completes (status=completed) — the failure was absorbed by the container, not the run", + "oracle": "api", + "verify": "run detail: status=completed; the http step inside the try region records status=failure with regionKind='try'; the catch-region update_record step records status=success with regionKind='catch'", + "evidence": "run-detail read" + }, + { + "clause": "the caught error binds to $error and lands in data: the task carries sync_status='failed' and a non-empty sync_error message", + "oracle": "api", + "verify": "task read after the run: sync_status='failed', sync_error interpolated from {$error.message}", + "evidence": "task read" + }, + { + "clause": "the retry policy actually ran before the catch: the failure is not instantaneous", + "oracle": "api", + "verify": "the run/step duration is at least the first backoff delay (>= ~1s per backoffMs:1000), evidencing at least one retry before the catch — note this oracle's weakness (duration, not a retry counter) in the evidence", + "evidence": "durationMs from the run detail" + }, + { + "clause": "unhandled: the probe run terminates status=failed with the run-level error populated", + "oracle": "api", + "verify": "probe run detail: status=failed (ExecutionStatus), error carries the http failure — NOT completed, NOT an empty error", + "evidence": "probe run-detail read" + }, + { + "clause": "both failures surface in the designer Runs panel: the failed step marked with its error message, catch-body steps nested under the container", + "oracle": "screenshot", + "verify": "Runs panel screenshots for both runs — the panel renders run/step errors (string run-level, {code,message} step-level) and nests region steps", + "evidence": "two Runs panel screenshots" + }, + { + "clause": "the unhandled failure is loud in the server log", + "oracle": "log", + "verify": "an ERROR-level line naming the probe flow/run accompanies the failed run", + "evidence": "log excerpt" + } + ], + "negative": [ + "an unhandled node failure that leaves its run status=completed, or leaves the run-level error empty, is a FAIL — a dead outbound call reporting success is the inert-automation failure shape (#1887)", + "a catch region that runs when the try did NOT fail is a FAIL of the container semantics — check the catch steps are absent from a successful run" + ], + "traps": ["wrong-panel", "single-datapoint"], + "source": [ + "examples/app-showcase/src/automation/flows/index.ts (ResilientSyncFlow, ADR-0031 try/catch/retry; canonical retry keys #4661)", + "packages/spec/src/automation/control-flow.zod.ts (TryCatchConfigSchema)", + "packages/spec/src/automation/execution.zod.ts (ExecutionStatus 'failed'; step status/error; regionKind)", + "objectui packages/app-shell/src/views/metadata-admin/previews/FlowRunsPanel.tsx (run-level string error vs step-level {code,message})", + "packages/runtime/src/route-ledger.ts (POST /automation — automation.create)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — splits handled (try_catch) vs unhandled failure into one contrast item with API + panel + log oracles", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "automation.screen-flow-roundtrip", + "title": "Screen flow round-trip: action trigger → paused run → rendered dialog → resume with inputs → persisted write", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": ["the Tasks list row action showcase_bulk_reassign wired to showcase_reassign_wizard (type screen, runAs user; screen node 'collect' with required field new_assignee; downstream update_record)"] + }, + "steps": [ + "boot showcase isolated (dogfood §0); sign in as the dev admin; open /apps/showcase_app/showcase_task", + "start capturing POSTs to /api/v1/automation/**", + "open the first row's action menu ([data-testid=row-action-trigger]) and click row-action-showcase_bulk_reassign", + "record the trigger POST; GET /api/v1/automation/showcase_reassign_wizard/runs to find the new run — status must be paused", + "GET /api/v1/automation/showcase_reassign_wizard/runs/:runId/screen and record the served screen contract", + "the console renders the screen as a dialog: screenshot it (heading 'New Assignee')", + "fill #ff-new_assignee with a unique value and Submit; record the resume POST body", + "re-read the task over /api/v1/data/showcase_task/:id — the assignee must equal the submitted value", + "negative probe: trigger the action again on another row, then POST /runs/:runId/resume directly with an EMPTY inputs bag" + ], + "acceptance": [ + { + "clause": "the flow action triggers a run that pauses at the screen node", + "oracle": "network", + "verify": "a POST matching /automation/[^/]+/trigger was issued and the run reads status=paused at node 'collect'", + "evidence": "network trace + run read" + }, + { + "clause": "GET …/runs/:runId/screen serves the persisted screen contract, including required on new_assignee", + "oracle": "api", + "verify": "the screen response lists the field new_assignee with required=true (the durable screen_json half of the contract)", + "evidence": "screen API read" + }, + { + "clause": "the console renders the paused screen as a dialog on the triggering page", + "oracle": "screenshot", + "verify": "dialog visible with the screen's heading and input — screenshot before any DOM assertions", + "evidence": "dialog screenshot" + }, + { + "clause": "Submit POSTs the resume route with the collected inputs and the run completes", + "oracle": "network", + "verify": "POST …/runs/:runId/resume carries {inputs:{new_assignee:}}; run re-read shows status=completed", + "evidence": "resume request body + run read" + }, + { + "clause": "the downstream update_record persisted: the task's assignee equals the submitted value on an authoritative read", + "oracle": "api", + "verify": "GET /api/v1/data/showcase_task/:id shows the new assignee (not just the refreshed grid cell)", + "evidence": "task read" + }, + { + "clause": "a resume missing the required screen input is refused with 400 and the run STAYS paused", + "oracle": "api", + "verify": "the empty-inputs resume answers 400 (screen-resume validation); the run re-reads as paused, and a later valid resume still works", + "evidence": "rejected resume response + run reads" + } + ], + "negative": [ + "a resume with the required new_assignee absent that answers 2xx or completes the run is a FAIL — the screen contract's required flag must be enforced server-side, not only by the dialog" + ], + "traps": ["automation-input", "hydration-race"], + "automated": { "kind": "e2e", "ref": "objectui e2e/live/screen-flow.spec.ts" }, + "source": [ + "objectui e2e/live/screen-flow.spec.ts (framework#3528 — the trigger → dialog → resume → refresh seam)", + "examples/app-showcase/src/automation/flows/index.ts (ReassignWizardFlow)", + "packages/services/service-automation/src/builtin/screen-resume-validation.test.ts + src/screen-input-contract.ts (400 on missing required inputs)", + "packages/runtime/src/route-ledger.ts (resume + getScreen routes)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — promotes the objectui live e2e's seam to a ledger item and adds the server-side required-input negative", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "automation.durable-suspend-restart", + "title": "Suspended runs persist to sys_automation_run, survive a cold restart, and resume — including nested (linked-run) pauses", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "a FILE-backed database (durability is structurally unreachable on the in-memory store — record the db path in the run env)", + "showcase_task_follow_up (wait timer PT1M) and showcase_project_closure → showcase_closure_signoff (approval inside subflow: the nested durable pause pair)" + ] + }, + "steps": [ + "boot showcase isolated against a file DB (dogfood §0); sign in as the dev admin", + "POST a showcase_task — showcase_task_follow_up suspends at its wait node (timer PT1M)", + "PATCH a showcase_project status→'completed' — showcase_project_closure parks at its subflow node and showcase_closure_signoff parks at its approval node (linked runs)", + "read the paused rows from sys_automation_run over the data API by run id: status, flow name, suspended node, and the rehydration payload", + "stop the server process entirely; cold-boot a second server over the SAME database file", + "re-read the same sys_automation_run rows — still present, still paused", + "resume path 1: let the wait timer elapse (the job service schedules a one-shot resume) and confirm the follow-up notify landed", + "resume path 2: POST /api/v1/approvals/requests/:id/approve on the closure sign-off — the child resumes, bubbles its decision output, and the parent completes", + "confirm the paused rows were CONSUMED on resume (replaced by run history rows), not left behind" + ], + "acceptance": [ + { + "clause": "each suspend persists a paused sys_automation_run row carrying everything a rehydration needs", + "oracle": "api", + "verify": "row read by id: status=paused, flow name, suspended nodeId, serialized variables/continuation present and non-empty", + "evidence": "row reads" + }, + { + "clause": "the paused rows and ordinary records survive a literal cold boot over the same database file", + "oracle": "api", + "verify": "post-restart reads return the identical paused rows (and the seeded data) — the #4518 wasm-driver class of loss (writes never reaching disk) shows up exactly here", + "evidence": "pre/post-restart row reads" + }, + { + "clause": "the timer wait resumes without manual intervention after restart and the downstream notify executes", + "oracle": "api", + "verify": "after the PT1M timer elapses on the NEW process: run status=completed and the reminder notification/inbox row exists for the assignee", + "evidence": "run read + notification read" + }, + { + "clause": "nested pause: the child parks at its approval node and the parent parks at its subflow node, correlated to the child run; the single approval decision completes BOTH and bubbles the child's decision output into the parent", + "oracle": "api", + "verify": "before: two paused runs (parent at 'signoff', child at 'ask_signoff'); after the approve: both completed, and the parent's notify carries {signoffResult.decision}", + "evidence": "before/after reads of both runs + the owner notification" + }, + { + "clause": "resume consumes the paused row: the pause row is gone and a run history row remains in its place", + "oracle": "api", + "verify": "post-resume: the paused-row id no longer reads as paused; the run's history row exists with terminal status", + "evidence": "row reads" + } + ], + "negative": [ + "a paused run that vanishes after restart — or a resume that reports success while the paused row still reads paused — is a FAIL against durability; both were real (#4420: every persist failed into an unread warn while the pause reported success)" + ], + "traps": ["stale-dist", "seed-data-thin"], + "automated": { "kind": "e2e", "ref": "packages/qa/dogfood/test/flow-durable-suspend.dogfood.test.ts" }, + "source": [ + "packages/qa/dogfood/test/flow-durable-suspend.dogfood.test.ts (#4470 · #4420 · #4518)", + "examples/app-showcase/src/automation/flows/index.ts (TaskFollowUpFlow, ProjectClosureFlow/ClosureSignoffSubflow — nested durable pause, linked-runs model)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — promotes the #4470 durable-suspend proof to a ledger item and adds the nested linked-run pair", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "automation.connector-dispatch-matrix", + "title": "connector_action dispatches through every registered connector kind, and the registry feeds the designer pickers", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "plugin connectors rest + slack (objectstack.config.ts) and declarative instances showcase_status_api (provider rest) + showcase_mcp_tools (provider mcp, in-repo stdio fixture server) from src/system/connectors/", + "the three dispatching flows: showcase_task_completed_rest_ping (rest), showcase_declarative_connector_ping (declarative rest, ADR-0097), showcase_mcp_connector_echo (declarative MCP, #3056)" + ], + "knownGaps": [ + "slack DELIVERY is not assertable on stock fixtures — TaskCompletedSlackFlow points at a placeholder channel with no real bot token, so the slack variant is registry-enumeration only" + ] + }, + "variants": ["rest (plugin)", "slack (plugin, enumeration only)", "showcase_status_api (declarative rest)", "showcase_mcp_tools (declarative MCP)"], + "steps": [ + "boot showcase isolated (dogfood §0); sign in as the dev admin", + "GET /api/v1/automation/connectors — record the descriptor list and each connector's action list (the MCP instance's actions must come from its tools/list handshake)", + "POST a showcase_task — fires showcase_declarative_connector_ping and showcase_mcp_connector_echo on create", + "PATCH the task status→'done' — fires showcase_task_completed_rest_ping", + "GET each of the three flows' newest run detail; capture the connector step's recorded request and response", + "open one connector_action node in the Studio flow-designer; screenshot the connector → action → input pickers and capture the network call that fills them", + "negative probe: author a scratch flow whose connectorConfig names connectorId 'nope_connector'; trigger it and read the run" + ], + "acceptance": [ + { + "clause": "the registry enumerates all four variants with their action lists", + "oracle": "api", + "verify": "GET /api/v1/automation/connectors lists rest, slack, showcase_status_api, showcase_mcp_tools; the MCP entry's actions include echo_upper", + "evidence": "connectors API read" + }, + { + "clause": "per-variant: each dispatch-capable variant's flow run captures the outbound call AND its response — rest ping and declarative ping both record GET /api/v1/health → {status:'ok'}; the MCP echo records structuredContent.upper === 'OBJECTSTACK' on the run output", + "oracle": "api", + "verify": "run-detail reads for the three flows: connector step success with the captured request/response; the MCP run's output variable echo.structuredContent.upper equals 'OBJECTSTACK'", + "evidence": "three run-detail reads" + }, + { + "clause": "the declarative path is metadata-only end to end: nothing registered showcase_status_api / showcase_mcp_tools in code — the provider materialized them at boot (ADR-0097)", + "oracle": "api", + "verify": "the two instances exist in the registry read AND dispatch in runs, while src/system/connectors declares them as pure metadata (cite the file in evidence)", + "evidence": "registry read + run reads + source citation" + }, + { + "clause": "the designer's connector/action/input pickers are fed by the live registry endpoint", + "oracle": "network", + "verify": "opening the node panel issues GET /api/v1/automation/connectors and the rendered options match the response", + "evidence": "network trace + panel screenshot" + } + ], + "negative": [ + "a connector_action naming an unregistered connectorId must FAIL its step with a named 'connector … not registered'-class error — a silent no-op success is a FAIL (the retired logger-backed stubs delivered nothing while reporting success, #4343)" + ], + "traps": ["stale-console-bundle", "single-datapoint"], + "source": [ + "examples/app-showcase/src/automation/flows/index.ts (TaskCompletedRestPingFlow, ShowcaseDeclarativeConnectorPingFlow, ShowcaseMcpConnectorEchoFlow)", + "examples/app-showcase/objectstack.config.ts (ConnectorRestPlugin/ConnectorSlackPlugin/ConnectorMcpPlugin + declarative connectors)", + "packages/runtime/src/route-ledger.ts (GET /automation/connectors)", + "ADR-0097 (provider-bound declarative connector instances)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — connector dispatch matrix over the plugin / declarative-rest / declarative-MCP kinds the showcase seeds, with the unregistered-id negative", "ref": "claude/platform-test-checklist-ocwugl" } + ] } ] } diff --git a/docs/qa/platform-checklist/areas/identity-auth.json b/docs/qa/platform-checklist/areas/identity-auth.json index a0a8e11e18..66096a2cd0 100644 --- a/docs/qa/platform-checklist/areas/identity-auth.json +++ b/docs/qa/platform-checklist/areas/identity-auth.json @@ -8,61 +8,414 @@ "title": "ssoEnforced login honors SSO on first paint — no password-wall flash", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", "surface": "browser", - "fixtures": { "app": "any", "requires": ["an env with ssoEnforced configured, cold-started"] }, + "personas": ["anonymous visitor at the login page", "env owner (break-glass password holder)"], + "fixtures": { + "app": "any", + "requires": [ + "an env with ssoEnforced configured, cold-started", + "ssoEnforced needs an IdP behind it: set auth.ssoOnlyMode: true (or OS_AUTH_SSO_ONLY=true) plus at least one configured oidcProviders[] entry — stock showcase ships neither, so this is a bespoke-env item" + ] + }, "steps": [ - "cold-start the server with ssoEnforced, open the login page fresh", - "capture the FIRST paint (screenshot as early as render settles)" + "configure ssoOnlyMode (or OS_AUTH_SSO_ONLY=true) with one OIDC provider entry and cold-start the server", + "BEFORE opening a browser, GET /api/v1/auth/config and record features.ssoEnforced, features.sso, and the socialProviders/oidc provider list — this is the flag the client will honor", + "open the login page in a fresh browser context and capture the FIRST paint (screenshot as early as render settles)", + "inspect the first paint for: the SSO sign-in surface, the ABSENCE of the email/password form and self-registration, and the PRESENCE of the break-glass 'use a password' link (the spec keeps the break-glass password endpoint enabled — the env owner retains an escape hatch)", + "simulate a hung sign-in (e.g. an IdP that never redirects back) and wait out the watchdog window; screenshot the recovery state", + "restart the server WITHOUT enforcement (ssoOnlyMode off) and capture the login first paint again as the other side of the gate" ], "acceptance": [ { - "clause": "the first paint honors ssoEnforced — the password form never flashes before the SSO redirect/button", + "clause": "the server advertises the enforcement: /api/v1/auth/config features.ssoEnforced is true (server truth for what the client honors)", + "oracle": "api", + "verify": "GET /api/v1/auth/config on the enforced env returns features.ssoEnforced true with at least one provider listed", + "evidence": "the /auth/config response" + }, + { + "clause": "the first paint honors ssoEnforced — the password form never flashes before the SSO surface", + "oracle": "screenshot", + "verify": "the earliest settled screenshot shows the SSO surface and no email/password form; no intermediate frame showed the password wall", + "evidence": "first-paint screenshot(s)" + }, + { + "clause": "self-registration is hidden under enforcement, but the break-glass password link remains — enforcement hides the local form, it does not brick the env owner", "oracle": "screenshot", - "verify": "first-paint screenshot shows the SSO surface; the settings API confirms the flag the client is honoring", - "evidence": "screenshot + /api/settings read" + "verify": "no sign-up affordance; the 'use a password' break-glass link is present (AuthFeaturesConfigSchema's own contract for ssoEnforced)", + "evidence": "annotated login screenshot" }, { "clause": "a hung sign-in recovers via the watchdog rather than stranding the page", "oracle": "screenshot", "verify": "after the watchdog window, the page offers a retry path (not a spinner forever)", "evidence": "post-window screenshot" + }, + { + "clause": "both sides of the gate: without enforcement the email/password form renders on first paint and features.ssoEnforced is false/absent", + "oracle": "api", + "verify": "the unenforced env's /auth/config + first-paint screenshot show the password form present", + "evidence": "the second /auth/config response + screenshot" } ], + "negative": [ + "a password form flashing for even one settled frame before the SSO redirect/button is the FAIL this item exists for — capture it, do not rationalize it as a hydration artifact without ruling the trap out twice" + ], "traps": ["hydration-race"], - "source": ["#3358 §6"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }] + "source": [ + "#3358 §6", + "packages/spec/src/system/auth-config.zod.ts (ssoOnlyMode + OS_AUTH_SSO_ONLY, break-glass endpoint stays enabled)", + "packages/spec/src/api/auth-endpoints.zod.ts (AuthFeaturesConfigSchema.ssoEnforced description)", + "packages/spec/src/kernel/public-auth-features.ts (ssoEnforced: login-surface flag; LoginForm hides password form + self-registration, break-glass link remains)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "identity-auth.phone-signin-surfaces", - "title": "A phone-based user's number shows across the identity surfaces", + "title": "A phone-based user's number shows across the identity surfaces, and the phone capability is advertised honestly", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", "surface": "browser", "personas": ["admin"], "fixtures": { "app": "showcase", - "requires": ["a seeded phone persona (Mei Phone — the seed itself failed silently until #3408; if she is absent, that regression has returned)"] + "requires": [ + "a seeded phone persona (Mei Phone — the seed itself failed silently until #3408; if she is absent, that regression has returned)", + "the exact seeded identity: usr_showcase_phone_demo / 'Mei Phone (demo)' / phone.demo@example.com / phone_number +8613800138000 (examples/app-showcase/src/security/seed-approval-demo.ts)" + ], + "knownGaps": [ + "the seeded persona's sign-in credential is not documented in the seed — driving an actual phone+password sign-in belongs to identity-auth.auth-method-matrix with a user created for that purpose; this item verifies the identity SURFACES" + ] }, "steps": [ - "create (or locate the seeded) phone-based user", - "check the create-result dialog, the All Users list, and the record detail" + "boot the showcase; FIRST verify the seed via the API: GET /api/v1/data/sys_user filtered on phone_number = '+8613800138000' — this is the #3408 regression tripwire, checked before any browser work", + "GET /api/v1/auth/config and record features.phoneNumber (the flag that gates the create_user phoneNumber param — the original #2871 fix)", + "open the All Users admin list and locate 'Mei Phone (demo)'; screenshot the list row showing the phone number column", + "open her record detail and screenshot the highlights showing the number", + "as admin, run the create-user action for a NEW phone-carrying user (the phoneNumber param is visible only when features.phoneNumber is true) and screenshot the create-result dialog", + "verify the new user via the API read (phone_number persisted, phone_number_verified present as a column)", + "if the deployment has the phoneNumber plugin OFF: verify the param is absent from the create-user dialog instead, and record which side of the gate this run exercised" ], "acceptance": [ { - "clause": "the phone number renders in all three surfaces", + "clause": "the seeded phone persona exists with the exact seeded number — server truth checked before the browser", + "oracle": "api", + "verify": "the sys_user read returns usr_showcase_phone_demo with phone_number +8613800138000", + "evidence": "the API response" + }, + { + "clause": "the phone number renders in all three surfaces: create-result dialog, All Users list row, and record detail", "oracle": "screenshot", "verify": "screenshots of dialog, list row, and detail highlights each showing the number", "evidence": "the three screenshots" + }, + { + "clause": "a created phone user PERSISTS the number — the dialog is not a client echo", + "oracle": "api", + "verify": "API read of the newly created user shows phone_number stored (the better-auth phone-number plugin's unique sys_user.phone_number column)", + "evidence": "the post-create API read" + }, + { + "clause": "the capability is advertised honestly: features.phoneNumber in /api/v1/auth/config matches whether the plugin is configured, and the create-user phoneNumber param is visible exactly when the flag is true (#2871's gate)", + "oracle": "api", + "verify": "/auth/config flag vs the create-user dialog's param set (dialog checked only after a screenshot confirms it rendered)", + "evidence": "the /auth/config response + dialog screenshot" + } + ], + "negative": [ + "if the seeded persona is missing, verdict is FAIL on the seed (regression of #3408), not blocked — the boot log will carry the insert error", + "the create-user dialog offering a phoneNumber param while features.phoneNumber is false is the #2871 class of failure (UI advertising a capability the runtime lacks) — FAIL" + ], + "traps": ["seed-data-thin", "wrong-persona"], + "source": [ + "#3358 §6", + "#3408", + "examples/app-showcase/src/security/seed-approval-demo.ts (PHONE_DEMO_USER)", + "packages/spec/src/system/auth-config.zod.ts (phoneNumber plugin: unique phone_number + phone_number_verified columns)", + "packages/spec/src/kernel/public-auth-features.ts (phoneNumber gates sys_user.actions.create_user.params.phoneNumber — #2871)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; encoded the #3408 silent-seed-failure as an explicit negative", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "identity-auth.auth-method-matrix", + "title": "Every supported auth method signs in when enabled, is absent when disabled, and is advertised exactly as configured", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["anonymous visitor", "provisioned user per method", "admin (for env configuration)"], + "fixtures": { + "app": "showcase", + "requires": [ + "per-variant env boots: the matrix flips AuthPluginConfigSchema flags (phoneNumber, twoFactor, deviceAuthorization, oidcProvider) and the socialProviders/oidcProviders maps — each variant names which boot it needs", + "an SMS service (@objectstack/service-sms) is required ONLY to make phone-OTP pass; its ABSENCE is itself a tested state (loud NOT_SUPPORTED)" + ], + "knownGaps": [ + "magic-link and passkeys: the server flags exist (AuthPluginConfigSchema.magicLink/passkeys) but objectui ships NO login UI for either — advertised-but-unconsumed, tracked in objectui#2514. Run these variants as blocked(dependency, objectui#2514) at the browser lane; the flag-advertisement clause still applies", + "the two spec files disagree on the device-flow paths (packages/spec/src/api/auth-endpoints.zod.ts: /device/request, /device/token, /device/approve vs packages/spec/src/system/auth-config.zod.ts: /device/code, /device/token, /device, /device/approve, /device/deny) — the runner must trust the LIVE server's routes and file the doc divergence if confirmed" + ] + }, + "steps": [ + "for each variant: boot with the method configured ON, GET /api/v1/auth/config, and record the advertisement (features.* flag, emailPassword block, socialProviders list)", + "email+password: POST /api/v1/auth/sign-up/email (when sign-up enabled), POST /api/v1/auth/sign-in/email, GET /api/v1/auth/get-session, POST /api/v1/auth/sign-out — capture each response and the session cookie lifecycle", + "phone+password: create a phone-carrying user with a known password, POST the phone sign-in (better-auth /sign-in/phone-number surface — 'always works' when the plugin is on per the spec), verify get-session identifies the user", + "phone OTP with NO SMS service configured: request /phone-number/send-otp and capture the loud NOT_SUPPORTED rejection (never a silent 200, never a hang)", + "SSO/OIDC: with an oidcProviders[] entry configured, verify the login page shows the SSO button (features.sso is refined to 'usable' — ≥1 provider); drive the authorization-code round trip or cite the pinned OIDC dogfood test per rule 6", + "device authorization: with deviceAuthorization on, drive the RFC 8628 flow (request device+user codes, approve in a signed-in browser, poll for the token) against the LIVE server's routes, recording which path spelling the server actually serves", + "2FA: with twoFactor on, enable it for a user (sys_user enable_two_factor action, gated on features.twoFactor), sign in, and verify the server-driven challenge (ADR-0069) interrupts before a session is granted", + "for each variant: re-boot with the method OFF, then (a) GET /auth/config and confirm the advertisement is gone, (b) screenshot the login page and confirm the affordance is gone, (c) fire the method's endpoint anyway and confirm a server-side refusal", + "record the per-variant verdict table (on-side result, off-side result, advertisement parity)" + ], + "acceptance": [ + { + "clause": "advertisement parity per variant: /api/v1/auth/config reflects the configuration exactly — no method advertised that is off, none hidden that is on", + "oracle": "api", + "verify": "for every variant, the on-boot and off-boot /auth/config reads match the config that booted them", + "evidence": "per-variant /auth/config pairs" + }, + { + "clause": "email+password round trip: sign-in issues a session that get-session confirms and sign-out invalidates (a post-sign-out get-session no longer returns the user)", + "oracle": "api", + "verify": "the four-call sequence with response codes and the session state at each step", + "evidence": "the captured sequence" + }, + { + "clause": "phone+password signs in when the plugin is on — the number is a first-class identifier, not a display field", + "oracle": "api", + "verify": "the phone sign-in returns a session for the created user; get-session identifies them", + "evidence": "the sign-in + session trace" + }, + { + "clause": "phone OTP without a deliverable SMS service fails LOUDLY (NOT_SUPPORTED) — the capability degrades to a named error, never a silent success or a hang", + "oracle": "api", + "verify": "the send-otp response is a non-2xx carrying the not-supported error; features.phoneNumberOtp is NOT advertised in /auth/config (only advertised when SMS is deliverable, #2780)", + "evidence": "the rejection + the /auth/config read" + }, + { + "clause": "the login page shows exactly the enabled methods — per-variant presence when on, absence when off", + "oracle": "screenshot", + "verify": "login screenshots per boot, checked against that boot's /auth/config", + "evidence": "the screenshot set" + }, + { + "clause": "a disabled method is refused SERVER-SIDE, not merely hidden — UI absence is a client courtesy; the server is the authority (ADR-0057 D10)", + "oracle": "api", + "verify": "firing each disabled method's endpoint returns a non-2xx (the plugin's routes are absent or refuse)", + "evidence": "the forged-request responses" + }, + { + "clause": "2FA is a server-driven gate: with 2FA enabled for the user, password sign-in alone does NOT yield a usable session until the challenge completes", + "oracle": "api", + "verify": "the sign-in response demands the challenge; get-session before completing it does not return an authenticated user", + "evidence": "the challenge-flow trace" + }, + { + "clause": "the OIDC authorization-code flow is pinned by automation — run the pin and cite its output rather than re-deriving the round trip by hand", + "oracle": "test", + "verify": "packages/qa/dogfood/test/oidc-authorization-code-flow.dogfood.test.ts passes on this build", + "evidence": "the test output" + } + ], + "negative": [ + "a silent 200 on any disabled method's endpoint is a FAIL — a gate that only hides the button is not a gate", + "phone OTP hanging or returning 2xx with no SMS service is a FAIL (the spec's own contract is 'loudly NOT_SUPPORTED')", + "ticking magic-link or passkeys as pass at the browser lane is a false positive — there is no UI to drive (objectui#2514); the honest verdict is blocked" + ], + "variants": [ + "email+password (POST /api/v1/auth/sign-in/email, /sign-up/email, /sign-out, /get-session)", + "phone+password (better-auth phone-number plugin sign-in surface)", + "phone OTP sign-in + reset (requires SMS service; loud NOT_SUPPORTED without — #2780)", + "enterprise SSO / generic OIDC (oidcProviders[] via genericOAuth; login button gated on usable providers)", + "social OAuth (socialProviders map, per-provider enabled)", + "device authorization grant (RFC 8628 — CLI/TV login)", + "two-factor (server-driven challenge, ADR-0069)", + "magic link (flag exists; no login UI — blocked, objectui#2514)", + "passkeys (flag exists; no login UI — blocked, objectui#2514)" + ], + "automated": { "kind": "e2e", "ref": "packages/qa/dogfood/test/oidc-authorization-code-flow.dogfood.test.ts" }, + "traps": ["hydration-race", "dispatcher-vs-hono-route", "wrong-persona"], + "source": [ + "packages/spec/src/system/auth-config.zod.ts (AuthPluginConfigSchema: phoneNumber/twoFactor/deviceAuthorization/magicLink/passkeys; socialProviders; oidcProviders; EmailAndPasswordConfigSchema)", + "packages/spec/src/api/auth-endpoints.zod.ts (AuthEndpointPaths; AuthFeaturesConfigSchema; device-flow response schemas)", + "packages/spec/src/kernel/public-auth-features.ts (flag semantics, gated inputs, objectui#2513/#2514 known gaps)", + "packages/qa/dogfood/test/oidc-authorization-code-flow.dogfood.test.ts" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new matrix item: per-method sign-in proof with both-sides gate checks and advertisement parity, grounded in the spec's plugin config + public feature registry", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "identity-auth.invitation-scope-gates", + "title": "Invitation issuance honors role-scope gates: delegated_admin can invite members but cannot mint admins; a plain member cannot invite at all", + "since": "v17", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["delegated_admin org member", "plain member", "tenant admin (setup only)"], + "fixtures": { + "app": "showcase", + "requires": [ + "a multi-org boot with the organization capability on (the sys_invitation actions — invite_user/cancel_invitation/resend_invitation — are gated on features.organization)", + "a member holding the delegated_admin org role (the ADR-0105 D8 principal; the pinned dogfood test shows the setup: mint the org, set membership roles via the better-auth-managed writer)" + ] + }, + "steps": [ + "prefer the pin (rule 6): run packages/qa/dogfood/test/delegated-admin-invite.dogfood.test.ts and capture its output — it proves the three-way contract end to end over real HTTP", + "browser lane, as the delegated_admin: open the invite affordance and issue a MEMBER-role invitation; capture the request and the created sys_invitation row via the data API", + "as the same principal, attempt an ADMIN-role invitation; capture the refusal", + "immediately read sys_invitation via the API and verify NO row (not even a pending one) was left behind by the refused attempt", + "as a plain member: verify the invite affordance is absent in the UI, then fire the invitation endpoint directly and capture the server refusal", + "as the invited member address, verify the pending invitation is visible/actionable where the product surfaces it, and check its status vocabulary against the spec enum", + "exercise cancel_invitation (or resend_invitation) on the pending row as the entitled persona and verify the status/state change via the API" + ], + "acceptance": [ + { + "clause": "a delegated_admin CAN issue a member invitation — the role reaches the endpoint (2xx) and a sys_invitation row exists with status pending", + "oracle": "api", + "verify": "the invite response + a sys_invitation read showing the pending row with the invitee email and role", + "evidence": "response + row read" + }, + { + "clause": "the SAME principal issuing role admin is refused — the role cap in beforeCreateInvitation holds (without it, admin-invite → auto-elevation → tenant admin is a four-step privilege escalation)", + "oracle": "api", + "verify": "the admin-role attempt returns non-2xx", + "evidence": "the refusal response" + }, + { + "clause": "the refusal is clean: NO invitation row is left behind by the refused attempt — no orphan pending admin invite that could later be accepted", + "oracle": "api", + "verify": "sys_invitation read immediately after the refusal shows no new row", + "evidence": "the read" + }, + { + "clause": "a plain member cannot invite AT ALL — proving it was the delegated_admin ROLE that opened the endpoint, not a general loosening", + "oracle": "api", + "verify": "the member's direct invitation request is refused server-side", + "evidence": "the forged-request response" + }, + { + "clause": "the UI shows invite affordances only to entitled personas — both sides captured", + "oracle": "screenshot", + "verify": "delegated_admin sees the invite affordance; plain member does not (screenshot first, then DOM)", + "evidence": "the two screenshots" + }, + { + "clause": "invitation lifecycle state uses the spec's vocabulary and transitions honestly (pending → accepted/rejected/expired; cancel/resend behave)", + "oracle": "api", + "verify": "row status is always one of the InvitationStatus enum values; the cancel/resend action's effect is visible in a fresh read", + "evidence": "the before/after reads" + } + ], + "negative": [ + "an admin-role invitation that returns success, or that leaves ANY row behind, is a FAIL of privilege-escalation severity — file immediately, P0-verify per RUNNER rule 7", + "UI-only enforcement (affordance hidden but the forged request succeeds) is a FAIL — the server is the authority (ADR-0057 D10)" + ], + "automated": { "kind": "e2e", "ref": "packages/qa/dogfood/test/delegated-admin-invite.dogfood.test.ts" }, + "traps": ["wrong-persona", "dispatcher-vs-hono-route"], + "source": [ + "packages/qa/dogfood/test/delegated-admin-invite.dogfood.test.ts (ADR-0105 D8 / #3697; the escalation chain the role cap blocks)", + "packages/spec/src/identity/organization.zod.ts (InvitationSchema, InvitationStatus enum)", + "packages/spec/src/kernel/public-auth-features.ts (organization feature gates sys_invitation invite/cancel/resend actions)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: invitation scope gates and lifecycle, pinned to the delegated-admin-invite dogfood test", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "identity-auth.admin-lifecycle-operations", + "title": "Admin user-lifecycle operations (ban/unban, set-password, impersonate) enforce, persist, and stay closed to non-admins", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["platform admin", "target user", "non-admin forger"], + "fixtures": { + "app": "showcase", + "requires": [ + "the better-auth admin plugin enabled (plugins.admin: true) — it exposes /admin/ban-user, /admin/unban-user, /admin/set-user-password, /admin/impersonate-user, /admin/stop-impersonating under the auth route and augments sys_user with role/banned/ban_reason/ban_expires and sys_session with impersonated_by", + "a disposable target user with a known password (create one; do not ban a seeded persona other items depend on)" + ] + }, + "steps": [ + "GET /api/v1/auth/config and confirm features.admin is advertised (opt-in flag; it gates the sys_user ban/unban/set-password/impersonate actions)", + "as platform admin, ban the target user with a reason via the sys_user action (or POST /api/v1/auth/admin/ban-user); read sys_user back and record banned/ban_reason", + "attempt to sign in as the banned user (POST /api/v1/auth/sign-in/email) and capture the refusal", + "unban, then verify the same sign-in now succeeds", + "set the target's password via the admin set-password (out-of-band recovery); verify the NEW password signs in and the OLD one is refused", + "impersonate the target from the admin surface; verify via the API that the impersonation session carries impersonated_by, and screenshot the console's impersonation state; stop impersonating and verify the admin's own session is restored", + "as a NON-admin, fire /admin/ban-user directly against the target and capture the refusal", + "run the audit-trail pin and capture its output for the attribution clause" + ], + "acceptance": [ + { + "clause": "ban persists and enforces: sys_user shows banned + ban_reason, and the banned user's sign-in is refused with a named error", + "oracle": "api", + "verify": "the sys_user read after the ban + the refused sign-in response", + "evidence": "row read + refusal" + }, + { + "clause": "unban restores access — the gate is reversible, not a tombstone", + "oracle": "api", + "verify": "post-unban sign-in succeeds for the same credentials", + "evidence": "the successful sign-in trace" + }, + { + "clause": "set-user-password rotates the credential: new password works, old password is refused", + "oracle": "api", + "verify": "both sign-in attempts captured after the rotation", + "evidence": "the two responses" + }, + { + "clause": "impersonation is attributed: the impersonated session records impersonated_by, and stop-impersonating returns the admin to their own session", + "oracle": "api", + "verify": "session read during impersonation shows impersonated_by = the admin; after stopping, get-session returns the admin again", + "evidence": "the two session reads" + }, + { + "clause": "the console makes the impersonation state visible while it is active — support sessions must not be silent", + "oracle": "screenshot", + "verify": "screenshot during impersonation shows the impersonated identity (and any impersonation indicator the console renders)", + "evidence": "the screenshot" + }, + { + "clause": "admin operations leave an attributable audit trail — pinned by automation", + "oracle": "test", + "verify": "packages/qa/dogfood/test/admin-identity-audit-trail.dogfood.test.ts passes on this build", + "evidence": "the test output" + }, + { + "clause": "the gate holds both ways: a non-admin's direct /admin/ban-user call is refused server-side (better-auth enforces the platform admin role internally)", + "oracle": "api", + "verify": "the forged call returns non-2xx and the target's sys_user row is unchanged", + "evidence": "the refusal + the unchanged row read" } ], - "negative": ["if the seeded persona is missing, verdict is FAIL on the seed (regression of #3408), not blocked — the boot log will carry the insert error"], - "traps": ["seed-data-thin"], - "source": ["#3358 §6", "#3408"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; encoded the #3408 silent-seed-failure as an explicit negative", "ref": "#3358" }] + "negative": [ + "a non-admin forged admin operation succeeding is a FAIL of the highest severity — apply RUNNER rule 7 (independent re-derivation) before acting on it", + "a ban that hides the user in the UI while their sign-in still works is a FAIL — the sign-in refusal is the enforcement, not the list filter" + ], + "automated": { "kind": "e2e", "ref": "packages/qa/dogfood/test/admin-identity-audit-trail.dogfood.test.ts" }, + "traps": ["wrong-persona", "shared-browser-tab"], + "source": [ + "packages/spec/src/system/auth-config.zod.ts (admin plugin: endpoint list, sys_user role/banned/ban_reason/ban_expires, sys_session.impersonated_by)", + "packages/spec/src/kernel/public-auth-features.ts (admin flag gates the sys_user lifecycle actions; SCIM forces it on — ADR-0071)", + "packages/qa/dogfood/test/admin-identity-audit-trail.dogfood.test.ts" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: admin lifecycle operations with persistence, enforcement, attribution and both-sides gate checks", "ref": "claude/platform-test-checklist-ocwugl" } + ] } ] } diff --git a/docs/qa/platform-checklist/areas/records-forms.json b/docs/qa/platform-checklist/areas/records-forms.json index 35927afbf5..740d91b02d 100644 --- a/docs/qa/platform-checklist/areas/records-forms.json +++ b/docs/qa/platform-checklist/areas/records-forms.json @@ -8,170 +8,1046 @@ "title": "Create → read → update → delete a record through the console UI", "since": "v15", "status": "active", - "revision": 1, + "revision": 2, "priority": "P0", "surface": "browser", - "personas": ["admin"], + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_account — writable standard object (sharingModel public_read_write), required name + status, format validations tax_id_format / billing_email_format (examples/app-showcase/src/data/objects/account.object.ts)" + ] + }, "steps": [ - "on a seeded showcase object, create a record through the New form", - "open its detail page; edit a field; save", - "delete it from the row/detail actions", - "after each mutation, re-read the row over the data API" + "sign in as seeded admin; open Accounts via its nav entry (/_console/apps/com.example.showcase/showcase_account)", + "click New; fill name with a distinctive value (os-qa-), industry, status=active; leave website empty; Save", + "capture the create request/response (expect POST /api/v1/data/showcase_account)", + "re-read server-side: GET /api/v1/data/showcase_account?$filter=[[\"name\",\"=\",\"os-qa-\"]] — field-by-field vs the submitted payload", + "open the record detail; edit exactly one field (annual_revenue); Save; capture the PATCH /api/v1/data/showcase_account/; re-read the full row via GET /api/v1/data/showcase_account/", + "open the record's History tab (screenshot first, then read entries)", + "delete via the row/detail action; confirm the dialog; re-read via the filtered GET", + "reload the list view and confirm final state" ], "acceptance": [ { - "clause": "each UI mutation is reflected in the server row (the API read is the oracle, not the grid repaint)", + "clause": "create returns success with an id and the API re-read shows every submitted value verbatim (no silent coercion/loss on any field)", + "oracle": "api", + "verify": "field-by-field diff of the POST payload vs the filtered GET re-read; the empty optional (website) stays empty, not defaulted", + "evidence": "create payload + re-read JSON" + }, + { + "clause": "the update persists ONLY the edited field — untouched fields byte-identical across before/after full-row reads", + "oracle": "api", + "verify": "diff of GET /api/v1/data/showcase_account/ before and after the single-field annual_revenue edit", + "evidence": "the two full-row reads" + }, + { + "clause": "delete removes the row authoritatively — the filtered API re-read returns 0 rows and the reloaded list no longer shows it", "oracle": "api", - "verify": "GET /api/v1/data/ after each step shows the created values, the edit, and finally the row gone", - "evidence": "the three API reads" + "verify": "GET ...?$filter=[[\"name\",\"=\",\"os-qa-\"]] returns total 0 post-delete", + "evidence": "the empty read + post-reload screenshot" }, { - "clause": "the list view reflects the changes after refresh without stale rows", + "clause": "the list reflects each mutation after a full reload with correct display values (grid repaint is NOT the oracle — the reload is)", "oracle": "screenshot", - "verify": "post-delete screenshot of the list shows the row absent", - "evidence": "screenshot" + "verify": "post-reload screenshots of the list at create and at delete", + "evidence": "screenshots" + }, + { + "clause": "record History reflects the create and the update with display values (option labels, localized dates, real actor), not raw audit payloads or phantom value→null rows", + "oracle": "dom", + "verify": "after a screenshot confirms the History tab rendered, read its entries — same contract objectui e2e/live/record-history-display.spec.ts pins on showcase Project", + "evidence": "screenshot + entries" + }, + { + "clause": "unicode round-trips: a create with a CJK name (e.g. os-qa--华宁) reads back byte-identical and is findable via list quick-search", + "oracle": "api", + "verify": "filtered GET returns the CJK name unmangled; $search finds it (the seed's 华宁科技 proves the pattern — ADR-0061)", + "evidence": "re-read JSON + search response" } ], + "negative": [ + "save with required status empty → the form blocks with a named field error AND no row is created (filtered API count stays 0) — a silent success is a FAIL", + "a direct API POST missing required status → 400 VALIDATION_FAILED with fields[] carrying code 'required' (server enforces, not just the form — packages/objectql/src/validation/record-validator.ts)", + "a create with tax_id violating the tax_id_format rule → named validation error, never a silently-stored bad value" + ], "traps": ["hydration-race", "automation-input"], - "source": ["dogfood-verification skill §3"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — standing P0 smoke", "ref": "#3358" }] + "source": [ + "dogfood-verification skill §3", + "examples/app-showcase/src/data/objects/account.object.ts (requiredness + format/conditional validations)", + "packages/runtime/src/route-ledger.ts (/data CRUD routes)", + "objectui: e2e/live/record-history-display.spec.ts" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — standing P0 smoke", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { - "id": "records-forms.master-detail-atomic-save", - "title": "Master + line items save as one atomic batch", + "id": "records-forms.field-type-matrix", + "title": "Field-type matrix: every FieldTypeSchema member renders its widget, accepts a valid value, round-trips over HTTP, and rejects invalid input", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_field_zoo — one field of (almost) every FieldType (examples/app-showcase/src/data/objects/field-zoo.object.ts); seeded rows 'Specimen — Full' and 'Specimen — Minimal'" + ], + "knownGaps": [ + "summary is NOT on the zoo (a roll-up needs a child object and the zoo is a leaf) — verify it on its real carriers showcase_invoice.total and showcase_expense_report.total_amount/approved_amount instead", + "f_user / f_users / f_owner cannot be seeded or written on a fresh boot with no signed-up users (sys_user rows come from sign-up, not seeds) — assign after creating a user, or record blocked(fixture)", + "f_secret writes fail closed unless an ICryptoProvider is registered (ADR-0100) — the seed deliberately omits it; on a stock boot verify the masked-read contract via the dogfood pin, not a raw UI write" + ] + }, + "variants": [ + "text", "textarea", "email", "url", "phone", "password", "secret", + "markdown", "html", "richtext", + "number", "currency", "percent", + "date", "datetime", "time", + "boolean", "toggle", + "select", "multiselect", "radio", "checkboxes", + "lookup", "master_detail", "tree", "user", + "image", "file", "avatar", "video", "audio", + "formula", "summary", "autonumber", + "composite", "repeater", "record", + "location", "address", "code", "json", "color", "rating", "slider", + "signature", "qrcode", "progress", "tags", + "vector" + ], + "steps": [ + "run the pinned HTTP round-trip matrix: pnpm --filter @objectstack/dogfood exec vitest run test/field-zoo-roundtrip.dogfood.test.ts (write vectors + expected read shapes live in test/field-zoo.matrix.ts)", + "boot showcase isolated; open Field Zoo (/_console/apps/com.example.showcase/showcase_field_zoo) and open 'Specimen — Full' in edit mode; screenshot the form before reading DOM", + "enumerate the rendered control for every f_* field and build a variant→widget table (date→date input, color→color input, richtext→editor, select/multiselect→pickers, image/file→upload, code→code editor, location/address→structured inputs, rating/slider/progress→their own controls, autonumber/formula→read-only)", + "through the form, change one representative value per family (a select, an array type, a temporal, a structured JSON), Save, and capture the PATCH", + "re-read the record via GET /api/v1/data/showcase_field_zoo/ and diff against the submitted values (arrays compared as sets)", + "POST an out-of-set select value (f_select: 'not-a-value') directly to /api/v1/data/showcase_field_zoo and capture the refusal", + "POST f_lookup with a fabricated account id and capture the refusal (#4441 dangling-reference gate)", + "verify f_formula and f_autonumber materialized server-side on a created row (formula = f_number × f_percent / 100)" + ], + "acceptance": [ + { + "clause": "every authorable field type in the matrix survives a real HTTP POST → GET round-trip with its declared value shape (arrays as sets; JSON object types as objects, not stringified)", + "oracle": "test", + "verify": "pnpm --filter @objectstack/dogfood exec vitest run test/field-zoo-roundtrip.dogfood.test.ts — green, with any it.fails (xfail) rows reported as the known type-fidelity gaps they are", + "evidence": "test run output" + }, + { + "clause": "PER-VARIANT: each of the 49 FieldTypeSchema members renders its real widget on the Specimen — Full form (not a generic text input), with per-variant evidence recorded in the variant→widget table", + "oracle": "dom", + "verify": "after the screenshot confirms render, match each f_* control against its declared type; every variant row in the table carries its own observed control + screenshot crop", + "evidence": "form screenshot + the 49-row variant→widget table" + }, + { + "clause": "constrained types reject invalid input server-side with a named error: out-of-set select → VALIDATION_FAILED with fields[].code 'invalid_option'; missing required name → code 'required'", + "oracle": "api", + "verify": "direct POSTs with the bad payloads return 400-class VALIDATION_FAILED envelopes naming the field; the row count does not grow", + "evidence": "refusal responses + before/after counts" + }, + { + "clause": "credential types mask on read: f_secret and f_password never echo plaintext — reads return the SECRET_MASK sentinel", + "oracle": "api", + "verify": "the dogfood matrix 'masked' checks (kind: 'masked' in test/field-zoo.matrix.ts) pass; any GET of the record shows the mask, not the written value", + "evidence": "test output + a raw GET excerpt" + }, + { + "clause": "computed/system types materialize without being written: f_autonumber is server-assigned and non-null; f_formula reads f_number × f_percent / 100 (matrix vector: 42 × 75 / 100 = 31.5)", + "oracle": "api", + "verify": "create via the matrix suite or by hand and read both fields back; the formula value matches the arithmetic", + "evidence": "the read JSON" + }, + { + "clause": "relational types (lookup / master_detail / tree) store a real reference id verbatim and refuse a dangling one", + "oracle": "api", + "verify": "matrix REFERENCE_TARGETS rows (showcase_account / showcase_project / showcase_category) round-trip their created ids; a fabricated id is rejected (#4441)", + "evidence": "test output + the dangling-id refusal" + }, + { + "clause": "summary is verified on its real carriers: showcase_invoice.total sums its lines server-side; showcase_expense_report shows the summaryOperations.filter variant (approved_amount ≠ total_amount on seeded EXP-2001)", + "oracle": "api", + "verify": "GET the seeded invoices/expense reports and check the roll-up columns against the seeded line arithmetic (EXP-2001: total 1500.50, approved 960 — seed comments carry the expected values)", + "evidence": "the reads + the seed-derived expected table" + } + ], + "negative": [ + "an out-of-set select value accepted with 200 is a FAIL (the server, not the picker, is the boundary)", + "a lookup/master_detail/tree write pointing at a nonexistent row accepted with 200 is a FAIL (#4441 closed exactly this hole)", + "any GET returning f_secret/f_password plaintext is a FAIL regardless of what the form shows" + ], + "traps": ["hydration-race", "automation-input", "stale-console-bundle"], + "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/field-zoo-roundtrip.dogfood.test.ts (+ field-zoo.matrix.ts vectors, field-zoo-value-shape.test.ts contract)" }, + "source": [ + "packages/spec/src/data/field.zod.ts (FieldType enum — 49 members, listed exhaustively in variants)", + "examples/app-showcase/src/data/objects/field-zoo.object.ts", + "examples/app-showcase/src/data/seed/index.ts (Specimen rows; expense/invoice roll-up expectations)", + "packages/qa/dogfood/test/field-zoo.matrix.ts", + "objectui: e2e/live/summary-rollup.spec.ts" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — deep-test variant matrix over FieldTypeSchema, pinned to the dogfood HTTP round-trip suite", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "records-forms.list-view-capabilities", + "title": "List-view capability matrix: filter, search, sort, pagination, saved views, inline edit, export, visualization switcher, bulk/row actions, conditional formatting", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_task with 10 seeded tasks (2 in_progress) and the saved views in examples/app-showcase/src/ui/views/task.view.ts (in_progress/urgent/done, tabular with string sort, grid with inlineEdit, bulk_actions, legacy_row_actions)", + "showcase_field_zoo views for conditional formatting + bulk-action gating (examples/app-showcase/src/ui/views/field-zoo.view.ts)", + "CJK seeds for search recall (account 华宁科技, contact 张伟)" + ] + }, + "variants": [ + "saved-view-filter", + "quick-search", + "sort", + "server-pagination", + "saved-view-switcher", + "inline-edit", + "export", + "visualization-switcher", + "bulk-actions", + "row-actions", + "conditional-formatting" + ], + "steps": [ + "open /_console/apps/com.example.showcase/showcase_task and screenshot the default All Tasks grid", + "switch to the in_progress saved view; capture the data request — expect $filter carrying the stored rule as an ObjectQL AST ([[\"status\",\"equals\",\"in_progress\"]]) and a 200; the count bar must read 2", + "quick-search 'huaning' on Accounts (or '张' on Contacts); capture the $search request and the hits (pinyin + CJK recall, ADR-0061)", + "open the tabular saved view (string-form sort 'estimate_hours desc' — the objectui#2601 fixture); confirm it renders and the request carries the sort", + "page a list past page 1; capture $top/$skip on every data request; confirm no unpaged full fetch", + "in the grid saved view (inlineEdit: true), edit one cell in place; save; capture the PATCH; re-read the row via API", + "on in_progress, export each declared format (csv, xlsx, json); capture the requests and files", + "open the visualization switcher on All Tasks; enumerate the offered types; switch through each", + "on bulk_actions, select rows and run showcase_mark_done (per-record) and showcase_recalc_selection (aggregate — ONE dispatch carrying params._selectedIds); on legacy_row_actions confirm both named row actions resolve to live menu entries", + "on Field Zoo, confirm the conditionalFormatting row highlight applies to Specimen — Full (f_lookup set) and not Specimen — Minimal" + ], + "acceptance": [ + { + "clause": "PER-VARIANT: every listed capability variant is exercised and carries its own captured evidence (request trace or screenshot) — no variant ticked by association", + "oracle": "network", + "verify": "one evidence artifact per variant in the run record, keyed by variant name", + "evidence": "per-variant trace/screenshot set" + }, + { + "clause": "a saved view's stored filter reaches $filter as an ObjectQL AST, the server answers 200, and the filtered count is server-computed (2 of 10 for in_progress)", + "oracle": "network", + "verify": "captured GET /api/v1/data/showcase_task?...$filter=[[\"status\",\"equals\",\"in_progress\"]] → 200; count bar matches the response total", + "evidence": "request/response trace + screenshot" + }, + { + "clause": "quick-search issues a server-side $search (not client filtering) and finds the CJK account via pinyin ('huaning' → 华宁科技)", + "oracle": "api", + "verify": "the captured request carries $search; the response contains the CJK row; the same query direct against GET /api/v1/data/showcase_account reproduces it", + "evidence": "request trace + response JSON" + }, + { + "clause": "paging issues $top/$skip requests; the full set is never fetched; a walked page sequence visits every row exactly once (deterministic paging)", + "oracle": "network", + "verify": "page 1 = $top=N; page 2 = $top=N&$skip=N; union of pages has no duplicate and no missing id", + "evidence": "the request URLs + the id-union check" + }, + { + "clause": "string-form sort ('estimate_hours desc') renders without error and the rows come back server-ordered (the objectui#2601 crash fixture stays green)", + "oracle": "network", + "verify": "the tabular view request succeeds; response row order is by estimate_hours descending", + "evidence": "trace + first-page rows" + }, + { + "clause": "inline edit persists through the API — the PATCH carries only the edited cell and a post-reload re-read shows it, with untouched fields unchanged", + "oracle": "api", + "verify": "diff full-row reads before/after the cell edit (same contract objectui e2e/live/inline-edit-polish-2572.spec.ts pins on Project)", + "evidence": "the two reads + the PATCH body" + }, + { + "clause": "export offers exactly the declared formats (csv, xlsx, json on in_progress) and the exported rows equal the FILTERED set, not the whole table", + "oracle": "network", + "verify": "the export menu lists the three exportOptions; each downloaded file contains the 2 in_progress rows", + "evidence": "menu screenshot + the three files" + }, + { + "clause": "the visualization switcher offers exactly the whitelisted six (grid, kanban, gallery, calendar, timeline, gantt — appearance.allowedVisualizations) and each re-renders the SAME task records", + "oracle": "dom", + "verify": "after screenshot, enumerate the dropdown entries; switch to each and confirm records render (map/chart are named views, correctly NOT in the switcher)", + "evidence": "dropdown screenshot + one screenshot per visualization" + }, + { + "clause": "bulk actions dispatch correctly by mode: per-record actions issue one dispatch per selected record; the aggregate def (showcase_recalc_selection, execution: 'aggregate') issues ONE request carrying every selected id in params._selectedIds", + "oracle": "network", + "verify": "count the captured POSTs against the selection size for each mode", + "evidence": "network trace of both runs" + }, + { + "clause": "legacy string rowActions resolve against the object's declared actions — showcase_recalc_estimate is a live entry (not a dead menu item) and showcase_quick_view appears exactly once (no dead duplicate)", + "oracle": "dom", + "verify": "after screenshot, open a row menu on the legacy_row_actions view; click each entry and confirm it dispatches (objectui#2960 contract)", + "evidence": "menu screenshot + dispatch traces" + } + ], + "negative": [ + "a saved view whose filter is refused (400 INVALID_FILTER) while the grid silently shows ALL rows unfiltered is a FAIL — the objectui#3431 regression shape; zero rows with a captured refusal is the honest symptom, unfiltered rows is the lie", + "userFilters leaking onto an OBJECT list view is a FAIL — ADR-0053 suppresses them there by design (filter elements belong to interface pages; objectui warns since #2220)", + "an export that returns the unfiltered table for a filtered view is a FAIL even though a file downloaded" + ], + "traps": ["hydration-race", "automation-input", "stale-console-bundle"], + "automated": { "kind": "e2e", "ref": "objectui: e2e/live/saved-view-filter.spec.ts, e2e/live/user-filters.spec.ts, e2e/live/inline-edit-polish-2572.spec.ts; packages/qa/dogfood/test/showcase-search.dogfood.test.ts" }, + "source": [ + "examples/app-showcase/src/ui/views/task.view.ts (saved views, sort string form, inlineEdit, exportOptions, bulk/row actions, allowedVisualizations)", + "examples/app-showcase/src/ui/views/field-zoo.view.ts (conditionalFormatting, gated bulk actions)", + "packages/spec/src/ui/view.zod.ts (UserActionsConfigSchema, AppearanceConfigSchema/VisualizationTypeSchema)", + "packages/spec/src/data/pagination-conformance.ts (deterministic paging property)", + "examples/app-showcase/src/data/seed/index.ts (10 tasks / 2 in_progress; CJK rows)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — deep-test capability matrix for list surfaces, pinned to the objectui live specs where they exist", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "records-forms.view-type-gallery", + "title": "View-type gallery: every ListViewSchema visualization renders seeded records", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_task views covering 8 types (examples/app-showcase/src/ui/views/task.view.ts: grid, board/kanban, cards/gallery, calendar, timeline, gantt, map, chart)", + "showcase_business_unit 'Organization Chart' for the 9th type, tree (examples/app-showcase/src/ui/views/business-unit.view.ts)", + "seeds sized to feed every view: 10 tasks across all 5 kanban columns with due/start/end/created dates and locations (src/data/seed/index.ts)" + ], + "knownGaps": [ + "task 'cover' (gallery coverField) is declared but deliberately unseeded (#4891 / ADR-0104 — a managed sys_file cannot honestly be seeded); the gallery renders coverless cards by design — do not fail the gallery variant on missing covers, upload one to prove the cover path" + ] + }, + "variants": ["grid", "kanban", "gallery", "calendar", "timeline", "gantt", "map", "chart", "tree"], + "steps": [ + "open /_console/apps/com.example.showcase/showcase_task and walk its named views: Grid, Board (Kanban), Cards (Gallery), Calendar, Activity Timeline, Schedule (Gantt), Work Locations (Map), Hours by Status (Chart)", + "on each: wait for render, screenshot FIRST, then read the DOM for the visualization's own structure (kanban columns, calendar cells, gantt bars, map markers, chart SVG)", + "on the kanban board, verify the groupByField columns match the 5 task statuses and every column is populated; check the summarizeField (estimate_hours) totals against an API aggregate", + "on calendar/timeline/gantt, spot-check 2 records' placement against their seeded due_date / created_at / start_date+end_date via API reads", + "on the chart view, confirm a real SVG is drawn from dataset showcase_task_metrics (not an empty canvas or a single bar from thin data)", + "open /_console/apps/com.example.showcase/showcase_business_unit and its Organization Chart view; verify the tree renders the seeded parent hierarchy", + "capture one screenshot per variant for the per-variant evidence set" + ], + "acceptance": [ + { + "clause": "PER-VARIANT: each of the 9 ListViewSchema types renders its REAL visualization (not a grid fallback) with seeded records, with its own screenshot in the evidence set", + "oracle": "screenshot", + "verify": "one screenshot per variant showing the visualization's characteristic structure; a variant that silently fell back to grid is a FAIL for that variant", + "evidence": "9 screenshots keyed by variant" + }, + { + "clause": "kanban groups by status with every seeded column populated, and the summarizeField per-column totals agree with a direct API aggregate of estimate_hours", + "oracle": "api", + "verify": "compare column headers/totals against GET /api/v1/data/showcase_task grouped client-side from the raw rows", + "evidence": "board screenshot + the aggregate check" + }, + { + "clause": "temporal views place records by their true date fields: calendar by due_date, timeline by created_at, gantt bars spanning start_date→end_date with progressField rendered", + "oracle": "api", + "verify": "for 2 sampled tasks, the placement matches the API-read dates", + "evidence": "screenshots + the two API reads" + }, + { + "clause": "map renders a marker per task with a location value; chart draws a non-empty SVG bound to the showcase_task_metrics dataset", + "oracle": "dom", + "verify": "after screenshots confirm render, count markers vs rows with location; assert svg element with plotted marks exists", + "evidence": "screenshots + DOM excerpts" + }, + { + "clause": "tree renders the seeded business-unit hierarchy (parent references as nesting), expandable without error", + "oracle": "dom", + "verify": "after screenshot, the rendered nesting matches parent fields read via GET /api/v1/data/showcase_business_unit", + "evidence": "screenshot + API read" + }, + { + "clause": "no variant throws a pageerror or renders an empty
(the automated smoke covers the page-level render for these surfaces)", + "oracle": "test", + "verify": "pnpm -C examples/app-showcase test:smoke — the All Views / Task Board / Calendar / Gallery / Schedule / Timeline / Work Map SURFACES stay green", + "evidence": "test run output" + } + ], + "negative": [ + "a visualization that renders as a plain grid (fallback) while its type claims kanban/calendar/gantt/etc. is a FAIL for that variant — 'it rendered something' is not the oracle", + "a chart that renders from a single datapoint proves little — note the weakness in evidence rather than ticking silently (single-datapoint trap)" + ], + "traps": ["hydration-race", "single-datapoint", "wrong-panel"], + "automated": { "kind": "e2e", "ref": "examples/app-showcase/e2e/showcase-smoke.spec.ts (page-level render for the view surfaces)" }, + "source": [ + "packages/spec/src/ui/view.zod.ts (ListViewSchema type enum: grid|kanban|gallery|calendar|timeline|gantt|map|chart|tree)", + "examples/app-showcase/src/coverage.ts (LIST_VIEW_TYPES — the 8 the task object demonstrates)", + "examples/app-showcase/src/ui/views/task.view.ts", + "examples/app-showcase/src/ui/views/business-unit.view.ts (tree)", + "examples/app-showcase/src/data/seed/index.ts (view-feeding seed shape)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — deep-test view-type gallery derived from the spec's own enum, 8 types on task + tree on business-unit", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "records-forms.form-view-gallery", + "title": "Form-view gallery: every FormViewSchema layout type renders and submits", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_task formViews: edit (simple), tabbed, wizard, split, quick (drawer) — examples/app-showcase/src/ui/views/task.view.ts" + ], + "knownGaps": [ + "the spec's 6th form type, 'modal', is not authored anywhere in the showcase (coverage.ts FORM_VIEW_TYPES deliberately lists 5) — record the modal variant blocked(fixture) rather than ticking or silently skipping it" + ] + }, + "variants": ["simple", "tabbed", "wizard", "split", "drawer", "modal"], + "steps": [ + "open a task record and its 'edit' (simple) form view; screenshot; verify the single 2-column section with the declared field order", + "open the tabbed form view; verify the three tabs (Overview / Schedule / Details) and that switching tabs preserves entered values", + "open the wizard form view; walk Basics → Assignment → Schedule with next/back; verify back preserves values and submit happens ONCE at the end", + "open the split form view; verify primary/secondary panes render their assigned sections side-by-side", + "trigger the quick (drawer) form; verify it renders as a side panel over the list with the 3 declared fields", + "on the simple form, set priority to 'urgent' and verify the notes field appears (FormField.visibleWhen CEL); set it back and verify notes hides", + "save an edit through each rendered variant; capture each save request; re-read via GET /api/v1/data/showcase_task/" + ], + "acceptance": [ + { + "clause": "PER-VARIANT: each authored form type renders its own layout mechanics (sections / tabs / steps / panes / side panel), with a screenshot per variant; the unauthored 'modal' variant is recorded blocked(fixture), never ticked", + "oracle": "screenshot", + "verify": "one screenshot per variant showing the characteristic layout; wizard shows step chrome, split shows two panes, drawer overlays the list", + "evidence": "per-variant screenshots" + }, + { + "clause": "the wizard enforces step order and issues exactly ONE save at the end (no per-step writes)", + "oracle": "network", + "verify": "network trace across the walk shows a single POST/PATCH at final submit", + "evidence": "the trace" + }, + { + "clause": "view-level visibleWhen works live: notes renders only while priority == 'urgent', full-width via span 'full'", + "oracle": "dom", + "verify": "after screenshot, toggle priority and assert notes mounts/unmounts (same family as objectui e2e/live/field-conditional-rules.spec.ts)", + "evidence": "before/after screenshots" + }, + { + "clause": "every save through every variant persists — API re-read shows the edited value and untouched fields unchanged", + "oracle": "api", + "verify": "GET /api/v1/data/showcase_task/ after each variant's save; diff against pre-save read", + "evidence": "the reads" + }, + { + "clause": "tab/step navigation never loses entered-but-unsaved values (switching tabs or going back a wizard step preserves the draft)", + "oracle": "dom", + "verify": "enter a distinctive value, navigate away and back within the form, read the control value", + "evidence": "screenshots at each hop" + } + ], + "negative": [ + "a wizard that lets Next past a required title with no named field error — or that silently writes per step — is a FAIL", + "counting the modal variant as passed because the other five rendered is a FAIL: it must be recorded blocked(fixture) with this item cited" + ], + "traps": ["hydration-race", "automation-input"], + "source": [ + "packages/spec/src/ui/view.zod.ts (FormViewSchema type enum: simple|tabbed|wizard|split|drawer|modal)", + "examples/app-showcase/src/coverage.ts (FORM_VIEW_TYPES — the 5 authored)", + "examples/app-showcase/src/ui/views/task.view.ts (formViews incl. visibleWhen on notes)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — deep-test form-view gallery derived from the spec enum; modal recorded as a standing fixture gap", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "records-forms.conditional-rules-header", + "title": "Header-level conditional rules: visibleWhen / requiredWhen / readonlyWhen on invoice fields, enforced on BOTH ends", "since": "v16", "status": "active", "revision": 1, "priority": "P1", + "surface": "mixed", + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_invoice conditional fields (examples/app-showcase/src/data/objects/invoice.object.ts): issued_on requiredWhen status in ['sent','paid']; tax_rate readonlyWhen status=='paid'; paid_on visibleWhen+requiredWhen status=='paid'; userActions edit disabledWhen / delete visibleWhen on paid", + "seeded invoices: INV-1002 (draft), INV-1001 (sent), INV-1003 (paid)" + ] + }, + "steps": [ + "open /_console/apps/com.example.showcase/showcase_invoice; open INV-1002 (draft) in its form", + "flip status draft → sent; observe issued_on gain a required marker; attempt save with issued_on empty; capture the block", + "set issued_on; save; re-read via GET /api/v1/data/showcase_invoice/", + "direct API negative: POST /api/v1/data/showcase_invoice with status 'sent' and no issued_on; capture the refusal", + "open INV-1003 (paid): verify paid_on is visible and required, tax_rate renders read-only", + "direct API: PATCH /api/v1/data/showcase_invoice/ with tax_rate: 99; then GET the row and compare tax_rate to its pre-PATCH value", + "on the invoice LIST, inspect INV-1003's row actions: Edit visible but disabled, Delete absent; compare a draft row's untouched menu", + "on INV-1002 (draft), verify paid_on is NOT rendered at all" + ], + "acceptance": [ + { + "clause": "requiredWhen reacts live in the form: leaving Draft marks issued_on required and blocks submit with a named field error while empty", + "oracle": "dom", + "verify": "after screenshot, assert the required marker + the field-level error on blocked submit (pinned by objectui e2e/live/field-conditional-rules.spec.ts and required-when-submit.spec.ts)", + "evidence": "screenshots + the blocked submit" + }, + { + "clause": "requiredWhen is enforced server-side: a direct POST with status 'sent' and no issued_on → VALIDATION_FAILED naming issued_on with code 'required' — the form is not the boundary", + "oracle": "api", + "verify": "the direct POST returns the 400-class envelope; invoice count for the test name stays 0", + "evidence": "refusal response + count check" + }, + { + "clause": "readonlyWhen locks the client AND the server drops the change: tax_rate on a paid invoice renders read-only, and a direct PATCH to it is silently DISCARDED — the persisted value must be unchanged on re-read (stripReadonlyWhenFields semantics: keep, not reject)", + "oracle": "api", + "verify": "GET before, PATCH tax_rate 99, GET after — before == after; note: a 200 on the PATCH is expected, the ORACLE is the unchanged re-read", + "evidence": "the two reads + the PATCH" + }, + { + "clause": "visibleWhen is honored: paid_on is absent from the draft form and present (and required) on the paid form", + "oracle": "dom", + "verify": "after screenshots of both forms, assert paid_on mounted only on paid", + "evidence": "both screenshots" + }, + { + "clause": "per-record row-action gating follows the same CEL truth: on paid rows Edit is visible-but-disabled (disabledWhen) and Delete is hidden (visibleWhen); draft rows keep the full menu", + "oracle": "dom", + "verify": "after screenshot, read both rows' action menus (objectui#2614; pinned by objectui e2e/live/list-row-action-cel.spec.ts)", + "evidence": "both row-menu screenshots" + }, + { + "clause": "the client and server evaluate ONE rule, not two: the same predicate that blocked the form blocks the API, and the field the client locked is the field the server strips", + "oracle": "api", + "verify": "cross-check clauses 1↔2 and 3: no case where the form blocks but the API accepts (or vice versa)", + "evidence": "the paired form + API results" + } + ], + "negative": [ + "a direct API POST of a 'sent' invoice without issued_on that succeeds is a FAIL (client-only enforcement)", + "a PATCH to tax_rate on a paid invoice whose new value PERSISTS is a FAIL — silent acceptance of a locked field is the defect this rule exists to stop", + "a Delete affordance on a paid invoice row is a FAIL even if clicking it would error later" + ], + "traps": ["hydration-race", "automation-input", "stale-console-bundle"], + "automated": { "kind": "e2e", "ref": "objectui: e2e/live/field-conditional-rules.spec.ts, e2e/live/required-when-submit.spec.ts, e2e/live/list-row-action-cel.spec.ts" }, + "source": [ + "examples/app-showcase/src/data/objects/invoice.object.ts (the B2 rules + userActions gating, with server-semantics comments)", + "packages/spec/src/data/field.zod.ts (requiredWhen/readonlyWhen/visibleWhen authoring surface)", + "packages/objectql/src/validation/rule-validator.ts (enforcement site)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — deep-test both-ends contract for header-level conditional rules on the seeded invoices", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "records-forms.conditional-rules-grid", + "title": "Grid-level conditional rules: row-scoped requiredWhen and parent-scoped readonlyWhen in the inline line-item grid", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_invoice_line rules (examples/app-showcase/src/data/objects/invoice.object.ts): description requiredWhen record.quantity >= 100 (ROW-scoped); product/quantity/unit_price readonlyWhen parent.status == 'paid' (PARENT-scoped); receipt Field.file upload cell (objectui#2360)", + "a draft invoice with editable lines (INV-1002) and a paid one (INV-1003)" + ] + }, + "steps": [ + "open INV-1002 (draft) — the inline Line Items grid renders via inlineEdit: 'grid'", + "on one line, raise quantity to 120 with description empty; observe the description cell flag required ON THAT ROW; screenshot", + "confirm a second line with quantity 1 stays unflagged and editable (per-row evaluation)", + "attempt to save the batch with the flagged description still empty; capture the block; then fill it and save; capture the batch request", + "server-side negative: submit the same shape directly to the API (line with quantity 120, no description) and capture the refusal", + "flip the header status to paid and save; observe product/quantity/unit_price cells lock; flip back to sent/draft and observe them unlock (live parent-scope re-evaluation)", + "on a draft line, upload a file into the receipt cell; save the batch; re-read the line via API and confirm the stored managed-file reference" + ], + "acceptance": [ + { + "clause": "row-scoped requiredWhen flags the cell per row: only the row crossing quantity >= 100 is marked, and the batch save is blocked with a named cell error while its description is empty", + "oracle": "dom", + "verify": "after screenshot, assert the required flag on row 1 and its absence on row 2; blocked save shows the error (pinned by objectui e2e/live/grid-conditional-rules.spec.ts)", + "evidence": "screenshots + blocked save" + }, + { + "clause": "the same rule is enforced on the server write path: a direct batch write with the violating line → VALIDATION_FAILED naming description; no partial rows land", + "oracle": "api", + "verify": "the direct submit returns the refusal envelope; line count for the invoice is unchanged", + "evidence": "refusal + before/after line reads" + }, + { + "clause": "parent-scoped readonlyWhen re-evaluates live against the header record: setting status to paid locks the three cells, reverting unlocks them — without a page reload", + "oracle": "dom", + "verify": "after screenshots at each state, assert cell editability (pinned by objectui e2e/live/grid-parent-rules.spec.ts)", + "evidence": "lock/unlock screenshots" + }, + { + "clause": "the receipt upload cell stores a real managed file: the API re-read of the line carries the sys_file reference and the file is retrievable", + "oracle": "api", + "verify": "GET the line post-save; the receipt field holds the file id, not an inline blob (ADR-0104 stored form; pinned by objectui e2e/live/grid-file-upload.spec.ts)", + "evidence": "line read + fetched file" + }, + { + "clause": "the saved batch persists: reloading the invoice shows the edited lines with their values, and untouched sibling lines byte-identical", + "oracle": "api", + "verify": "diff all lines before/after the save; only the edited line changed", + "evidence": "the before/after reads" + } + ], + "negative": [ + "a batch save that succeeds with quantity 120 and empty description is a FAIL on both the client clause and the server clause", + "cells still editable on a paid invoice (or still locked after reverting) is a FAIL — the parent scope must re-evaluate live" + ], + "traps": ["hydration-race", "automation-input"], + "automated": { "kind": "e2e", "ref": "objectui: e2e/live/grid-conditional-rules.spec.ts, e2e/live/grid-parent-rules.spec.ts, e2e/live/grid-file-upload.spec.ts" }, + "source": [ + "examples/app-showcase/src/data/objects/invoice.object.ts (row/parent-scoped rules on the line object; ADR-0036 / #1581)", + "objectui: e2e/live/grid-conditional-rules.spec.ts, grid-parent-rules.spec.ts" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — deep-test grid conditional rules split from the header item (different scopes, different pins)", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "records-forms.cascading-options", + "title": "Cascading and gated select options: dependsOn + per-option visibleWhen, client narrows / server rejects", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["seeded admin (admin@objectos.ai / admin123)", "non-admin user (sign one up — sys_user rows cannot be seeded)"], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_cascade (examples/app-showcase/src/data/objects/cascading-select.object.ts): province dependsOn ['country'] with per-option visibleWhen (cn → zj/gd, us → ca/tx); tier option 'restricted' gated on 'admin' in current_user.positions; sharingModel public_read_write", + "showcase_invoice.contact dependsOn ['account'] — the dependent LOOKUP twin (contact picker scoped to the chosen account's people; seed gives Northwind 26 contacts, Contoso 2, Fabrikam 1)" + ] + }, + "steps": [ + "open a New Cascading Select form (/_console/apps/com.example.showcase/showcase_cascade or its nav entry)", + "verify province is gated (empty/disabled) until a country is chosen", + "pick country=cn; enumerate province options (expect exactly Zhejiang, Guangdong); switch to us; re-enumerate (California, Texas) and verify a previously chosen cn province CLEARED", + "as admin, enumerate tier options (Standard + Restricted); save a record with tier=restricted", + "direct API negative: POST /api/v1/data/showcase_cascade with country 'cn' and province 'ca'; capture the refusal; then POST province 'zj' and capture the success", + "as the non-admin user: enumerate tier options in the form (Restricted absent), then POST tier='restricted' directly and capture the refusal", + "on a New Invoice, verify the contact picker is empty-scoped before an account is chosen; pick Northwind and capture the picker's data request (scoped to Northwind's contacts); switch to Contoso and verify the candidate set changes" + ], + "acceptance": [ + { + "clause": "dependsOn gates the dependent field until its driver has a value, and changing the driver re-filters the offered set live, clearing a now-invalid selection", + "oracle": "dom", + "verify": "after screenshots, enumerate the offered options at each country state (pinned by objectui e2e/live/cascading-options.spec.ts: 'province options re-filter live as country changes, and the stale value clears')", + "evidence": "option enumerations + screenshots" + }, + { + "clause": "the server rejects an out-of-set submitted option with VALIDATION_FAILED and fields[] carrying {field: 'province', code: 'invalid_option'} — and accepts the in-set one", + "oracle": "api", + "verify": "the two direct POSTs from the steps; refusal envelope must name the field and code (pinned by the same spec's API half; server site: objectql evaluateOptionVisibility, objectui#2284)", + "evidence": "both responses" + }, + { + "clause": "BOTH sides of the role gate: admin sees and can persist tier='restricted'; the non-admin neither sees it NOR can submit it — the direct non-admin POST is refused server-side", + "oracle": "api", + "verify": "admin create re-reads with tier='restricted'; non-admin POST returns the invalid_option refusal (current_user bound from the request on authenticated writes)", + "evidence": "admin re-read + non-admin refusal + both option enumerations" + }, + { + "clause": "the dependent LOOKUP twin works: the invoice contact picker issues account-scoped candidate requests, and switching accounts changes the candidate set (26 for Northwind vs 2 for Contoso)", + "oracle": "network", + "verify": "captured picker requests carry the account scope; candidate counts match the seeded spread", + "evidence": "picker request traces + counts" + }, + { + "clause": "a legal cascade selection persists: create with country=cn, province=zj re-reads verbatim over the API after reload", + "oracle": "api", + "verify": "GET the created row; both values present", + "evidence": "the re-read" + } + ], + "negative": [ + "an out-of-set province accepted with 200 is a FAIL — client hiding is UX, the objectql rule-validator is the boundary", + "a non-admin's direct tier='restricted' POST accepted is a FAIL even though their picker hid the option (UI absence alone is a client courtesy — RUNNER rule 4)" + ], + "traps": ["hydration-race", "automation-input", "wrong-persona"], + "automated": { "kind": "e2e", "ref": "objectui: e2e/live/cascading-options.spec.ts" }, + "source": [ + "examples/app-showcase/src/data/objects/cascading-select.object.ts (the B3 / #1583 fixture, with both-sides contract in its header comment)", + "examples/app-showcase/src/data/objects/invoice.object.ts (contact dependsOn account)", + "examples/app-showcase/src/data/seed/index.ts (contact spread per account)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — deep-test dynamic options: cascade, role gate, dependent lookup, server-side rejection", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "records-forms.master-detail-atomic-save", + "title": "Master + line items save as one atomic batch, with server-side roll-up", + "since": "v16", + "status": "active", + "revision": 2, + "priority": "P1", "surface": "browser", - "personas": ["admin"], - "fixtures": { "app": "showcase", "requires": ["a master-detail pair with an inline child grid (e.g. expense report + lines)"] }, + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_invoice + showcase_invoice_line with inlineEdit: 'grid' on the master_detail field (examples/app-showcase/src/data/objects/invoice.object.ts) — every New/Edit Invoice form renders the editable Line Items grid", + "seeded products for the catalog lookup (SKUs incl. SERVICE-HR, WIDGET-A; picking one auto-fills description + unit_price)" + ] + }, "steps": [ - "edit the master and several child lines in one form session, save once", - "repeat with one child line made deliberately invalid, save" + "open New Invoice (/_console/apps/com.example.showcase/showcase_invoice → New); fill name os-qa-, account=Northwind, status=draft", + "add two lines in the inline grid: pick a product on each (observe description/unit_price auto-fill), set quantities; watch amount recompute read-only as qty × unit_price", + "Save ONCE; capture the network trace of the save", + "re-read master + lines via GET /api/v1/data/showcase_invoice?$filter=[[\"name\",\"=\",\"os-qa-\"]] and the lines by the returned invoice id; check invoice.total against the sum of line amounts", + "add a third line via the edit form and save; re-read invoice.total; delete that line; re-read total again", + "repeat the create with one child made deliberately invalid (product empty on a line); attempt save; capture the failure", + "verify no partial writes: filtered GET for the failed invoice name returns 0 rows and no orphan lines exist" ], "acceptance": [ { - "clause": "the happy-path save lands master + children in one batch request", + "clause": "the happy-path save lands master + children in ONE atomic batch request (not N sequential writes), with the child ops referencing the parent", "oracle": "network", - "verify": "the save issues a single batch call (not N sequential writes); all rows readable afterwards", - "evidence": "network trace of the save" + "verify": "the save issues a single batch call whose payload contains the parent and both line ops (pinned by objectui e2e/live/master-detail.spec.ts: 'Create submits the populated parent in one atomic batch' / 'includes the child op referencing the parent')", + "evidence": "the batch request payload" + }, + { + "clause": "all rows are readable afterwards and correct: master + 2 lines re-read via API with the entered values; product pick auto-filled description/unit_price persisted", + "oracle": "api", + "verify": "field-by-field diff of the re-read against what was entered/auto-filled", + "evidence": "the reads" + }, + { + "clause": "invoice.total is a SERVER-side roll-up: computed on the atomic create, recomputed when a line is added and again when it is deleted", + "oracle": "api", + "verify": "total == sum(line.amount) after each mutation (pinned by objectui e2e/live/summary-rollup.spec.ts: computed on atomic create; recomputes on child add/delete)", + "evidence": "the three total reads" + }, + { + "clause": "a failing child aborts the WHOLE save — master not created, sibling lines not written, and the form surfaces a named error", + "oracle": "api", + "verify": "after the failed save, the filtered GET returns 0 invoices and no line rows reference a phantom parent", + "evidence": "the empty reads + the error screenshot" + }, + { + "clause": "the relationship-derived subform/grid renders on the standard New form with NO hand-built page (derived from the master_detail declaration)", + "oracle": "dom", + "verify": "after screenshot, the Line Items grid is present on the stock New Invoice form (pinned by objectui e2e/live/form-view-subforms.spec.ts)", + "evidence": "form screenshot" }, { - "clause": "a failing child aborts the WHOLE save — no partial writes", + "clause": "the amount expression recomputes live client-side (read-only cell) and the computed value persists as the stored column the roll-up reads", "oracle": "api", - "verify": "after the failed save, API reads show master and siblings unchanged", - "evidence": "API reads proving no row moved" + "verify": "change qty, watch amount update without save; after save the API row carries qty × unit_price", + "evidence": "screenshot + line read" } ], - "negative": ["the invalid-child case above IS the negative side — it must not partially commit"], - "traps": ["automation-input"], - "source": ["#3358 §4"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }] + "negative": [ + "the invalid-child case must not partially commit: ANY surviving master or sibling row after the failed save is a FAIL — check by API read, not by the grid", + "a save that issues one write per row (N requests) is a FAIL of the atomicity clause even when all rows land" + ], + "traps": ["automation-input", "hydration-race"], + "automated": { "kind": "e2e", "ref": "objectui: e2e/live/master-detail.spec.ts, e2e/live/summary-rollup.spec.ts, e2e/live/form-view-subforms.spec.ts" }, + "source": [ + "#3358 §4", + "examples/app-showcase/src/data/objects/invoice.object.ts (inlineEdit grid, amount expression, total summary)", + "examples/app-showcase/src/data/seed/index.ts (product catalog rows)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "records-forms.related-list-server-pagination", - "title": "Related lists page on the server, never fetch every child row", + "title": "Related lists derive from the relationship and page on the server — never fetch every child row", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "browser", - "personas": ["admin"], - "fixtures": { "app": "showcase", "requires": ["a parent record with more children than one page (e.g. Northwind account with 26 contacts)"] }, + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { + "app": "showcase", + "requires": [ + "Northwind account with 26 seeded contacts (2 named + 24 'Prospect NN' rows authored precisely so the related list exceeds a page — examples/app-showcase/src/data/seed/index.ts, objectui#2711)", + "showcase_invoice.account declares relatedList: 'primary' with relatedListTitle 'Invoices' and relatedListColumns [name, status, total, issued_on] (examples/app-showcase/src/data/objects/invoice.object.ts)" + ] + }, "steps": [ - "open the parent detail page's related list", - "click next-page; capture the data requests" + "open the Northwind account detail page (/_console/apps/com.example.showcase/showcase_account → Northwind)", + "screenshot; enumerate the related-list tabs (expect relationship-derived lists incl. Contacts and the declared Invoices tab)", + "open the Contacts related list; capture the page-1 data request", + "click next-page; capture the page-2 request; note the pager text", + "diff page-1 and page-2 row ids for overlap", + "open the Invoices related tab; verify its title and columns match the declaration on the lookup field (name, status, total, issued_on)", + "capture every data request issued by the detail page and check each child query for a $top bound" ], "acceptance": [ { "clause": "paging issues server-side $top/$skip requests scoped by the parent filter; the full child set is never fetched", "oracle": "network", - "verify": "page 1 = top=N&filter=[parent]; next page = top=N&skip=N; no request without top", - "evidence": "the captured request URLs + the pager reading 'page 2 of M'" + "verify": "page 1 = $top=N + parent filter; page 2 = $top=N&$skip=N; NO child-list request without $top anywhere on the page", + "evidence": "the captured request URLs" + }, + { + "clause": "the pager reflects the server total (26 Northwind contacts), and pages partition the set — no row repeated or skipped across pages", + "oracle": "network", + "verify": "pager reads 'page 2 of M' consistent with total 26; id sets of page 1 and 2 are disjoint", + "evidence": "pager screenshot + the id diff" + }, + { + "clause": "related lists are DERIVED from the relationship declaration with no hand-built page: the Account detail auto-renders them (Projects is the pinned example)", + "oracle": "dom", + "verify": "after screenshot, the related tabs exist on the stock detail page (pinned by objectui e2e/live/detail-related-list.spec.ts)", + "evidence": "detail screenshot" + }, + { + "clause": "the declared relatedList config is honored: the Invoices tab carries the authored title and exactly the authored columns", + "oracle": "dom", + "verify": "after screenshot, read the tab label and column headers; compare to relatedListTitle/relatedListColumns on the lookup field", + "evidence": "tab screenshot + declaration excerpt" + }, + { + "clause": "row navigation works from the related list: clicking a child row opens its record, and the child re-reads via its own API id", + "oracle": "api", + "verify": "GET /api/v1/data/showcase_contact/ matches the row navigated to", + "evidence": "navigation screenshot + the read" } ], + "negative": [ + "any child-list request WITHOUT a $top bound is a FAIL even when the rendered page looks right — the unpaged fetch is the defect", + "a pager total that disagrees with a direct filtered API count (GET /api/v1/data/showcase_contact?$filter=[[\"account\",\"=\",\"\"]]) is a FAIL" + ], "traps": ["hydration-race"], - "automated": null, - "source": ["#3358 §4 (evidence run captured exactly this trace)"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the #3358 evidence run", "ref": "#3358" }] + "automated": { "kind": "e2e", "ref": "objectui: e2e/live/detail-related-list.spec.ts" }, + "source": [ + "#3358 §4 (evidence run captured exactly this trace)", + "examples/app-showcase/src/data/seed/index.ts (the 26-contact Northwind fixture, authored for objectui#2711)", + "examples/app-showcase/src/data/objects/invoice.object.ts (relatedList declaration)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from the #3358 evidence run", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "records-forms.action-param-widgets", - "title": "Action params render their real widgets, not text inputs", + "title": "Action params render their real widgets, and the param contract is enforced at dispatch", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", - "surface": "browser", - "personas": ["admin"], - "fixtures": { "app": "showcase", "requires": ["the Action Param Gallery action on the Field Zoo row menu"] }, + "surface": "mixed", + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_action_param_gallery on showcase_field_zoo (examples/app-showcase/src/ui/actions/index.ts): p_text (text, required), p_richtext, p_priority (select), p_date, p_account (lookup→showcase_account), p_assignee (lookup→sys_user), p_color, p_reference (autonumber), p_cover (image, accept image/*, maxSize 5MB), p_attachments (file, multiple, accept pdf+image, maxSize 10MB)" + ] + }, + "variants": [ + "p_text (text)", + "p_richtext (richtext)", + "p_priority (select)", + "p_date (date)", + "p_account (lookup → showcase_account)", + "p_assignee (lookup → sys_user)", + "p_color (color)", + "p_reference (autonumber)", + "p_cover (image)", + "p_attachments (file, multiple)" + ], "steps": [ - "run the gallery action; wait for the dialog to render (screenshot first)", - "read each param's rendered control from the dialog DOM" + "open Field Zoo (/_console/apps/com.example.showcase/showcase_field_zoo); run the gallery action from the Specimen — Full row menu", + "wait for the dialog; screenshot FIRST", + "enumerate each param's rendered control from the dialog DOM and build the variant→control table", + "read accept/multiple off the two upload inputs as real DOM attributes", + "fill a conformant bag (p_text set, valid p_priority) and confirm; capture the dispatch POST /api/v1/actions/showcase_field_zoo/showcase_action_param_gallery", + "re-open and confirm with p_text EMPTY; capture the outcome", + "direct API negative: POST the dispatch route with a malformed bag (e.g. p_priority out-of-set) and capture the refusal" ], "acceptance": [ { - "clause": "each param type maps to its real widget: date→input[type=date], color→input[type=color], richtext→editor, select→picker, file/image→input[type=file] with accept/multiple as declared, autonumber→read-only server-assigned", + "clause": "PER-VARIANT: each declared param renders its real widget (date→date input, color→color input, richtext→editor, select→picker, lookup→record picker, file/image→input[type=file], autonumber→read-only server-assigned) — recorded per variant in the table", "oracle": "dom", - "verify": "after screenshot confirms render, enumerate controls and match the declared param types; accept/multiple must be real DOM attributes", - "evidence": "screenshot + the param→control table" + "verify": "after the screenshot confirms render, match each control against the declared param type; every variant row carries its own observed control", + "evidence": "screenshot + the 10-row param→control table" + }, + { + "clause": "accept and multiple are REAL DOM attributes on the upload inputs, matching the declaration (p_cover: image/*; p_attachments: multiple + application/pdf,image/*)", + "oracle": "dom", + "verify": "read the attributes off both inputs post-render", + "evidence": "DOM excerpt" + }, + { + "clause": "the declared param contract is enforced at dispatch BEFORE the body runs (strict since 17.0, #3438): a malformed bag is rejected 400-class; the conformant bag passes and the body echoes the received keys", + "oracle": "api", + "verify": "the two direct POSTs against /api/v1/actions/showcase_field_zoo/showcase_action_param_gallery (pinned by packages/qa/dogfood/test/action-params-contract.dogfood.test.ts, ADR-0104 D2)", + "evidence": "both responses" + }, + { + "clause": "required p_text is enforced on both ends: the dialog blocks confirm (or errors) with it empty, and a direct dispatch without it is refused server-side", + "oracle": "api", + "verify": "UI attempt + direct POST both refuse with a named error; no action execution recorded", + "evidence": "UI screenshot + API refusal" } ], - "negative": ["maxSize is enforced in JS, not as a DOM attribute — do NOT claim it from attribute absence; it needs an oversized-upload attempt to verify"], - "traps": ["hydration-race"], - "source": ["#3358 §4 (evidence table)", "#3393"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358, keeping its maxSize caveat as a negative-side note", "ref": "#3358" }] + "negative": [ + "maxSize is enforced in JS, not as a DOM attribute — do NOT claim it from attribute absence; it needs an oversized-upload attempt (covered by records-forms.upload-guard-blocks-confirm)", + "a malformed param bag that reaches the action body (echo shows the bad key accepted) is a FAIL — the dispatch gate, not the widget, is the boundary" + ], + "traps": ["hydration-race", "automation-input"], + "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/action-params-contract.dogfood.test.ts; objectui: e2e/live/action-modal.spec.ts" }, + "source": [ + "#3358 §4 (evidence table)", + "#3393", + "examples/app-showcase/src/ui/actions/index.ts (the gallery action's declared params)", + "ADR-0059 (param-dialog widgets)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358, keeping its maxSize caveat as a negative-side note", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "records-forms.upload-guard-blocks-confirm", - "title": "Confirm stays disabled while a file param is still uploading", + "title": "Confirm stays disabled while a file param is still uploading; maxSize is enforced by a real oversized attempt", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", "surface": "browser", - "personas": ["admin"], + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_action_param_gallery's p_attachments (maxSize 10MB) and p_cover (maxSize 5MB) — the ADR-0059 upload-guard fixtures (examples/app-showcase/src/ui/actions/index.ts)" + ] + }, "steps": [ - "open an action dialog with a file param; start a real upload large/slow enough to catch mid-flight", - "attempt to confirm while the upload is in progress" + "open the gallery action dialog from the Field Zoo row menu (screenshot first)", + "prepare a real file large enough to catch mid-flight (~8MB, under the 10MB p_attachments limit); start the upload into p_attachments", + "while the upload is in flight, attempt to click Confirm; capture the network trace across the attempt", + "wait for upload completion; confirm; capture the dispatch request", + "re-open the dialog; attempt an OVERSIZED file (>10MB) into p_attachments and (>5MB) into p_cover; capture the client's response", + "verify no upload request was issued for the oversized attempts" ], "acceptance": [ { "clause": "the Confirm control is actually disabled mid-upload and enables on completion — proven by catching a real upload in flight, not by reading the helper label", "oracle": "dom", - "verify": "during the in-flight window the button carries disabled state and clicking issues no request; after completion the confirm succeeds", + "verify": "during the in-flight window the button carries disabled state AND clicking issues no dispatch request; after completion the confirm succeeds", "evidence": "mid-flight screenshot + network trace showing no premature submit" + }, + { + "clause": "the completed dispatch carries the uploaded file reference and the action executes", + "oracle": "network", + "verify": "the POST /api/v1/actions/showcase_field_zoo/showcase_action_param_gallery after completion returns success with the file param populated", + "evidence": "the dispatch trace" + }, + { + "clause": "maxSize is enforced by attempt: an oversized file is rejected with a named client error BEFORE any upload request is issued (both the 10MB and the 5MB limits)", + "oracle": "network", + "verify": "the oversized attempts produce a visible error and zero upload requests in the trace", + "evidence": "error screenshots + the empty trace window" + }, + { + "clause": "a rejected oversized file leaves the dialog usable: a subsequent valid file uploads and confirms normally", + "oracle": "dom", + "verify": "after the rejection, repeat a valid upload and confirm", + "evidence": "final success screenshot" } ], - "traps": ["automation-input"], - "source": ["#3358 §4 ('leaving it unticked on the strength of a label')", "ADR-0059"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — codifies the #3358 refusal to tick from a label into the oracle itself", "ref": "#3358" }] + "negative": [ + "a confirm click mid-upload that ISSUES the dispatch is a FAIL even if the server would cope", + "an oversized file that starts uploading (any upload request observed) is a FAIL — the guard must reject before the wire" + ], + "traps": ["automation-input", "hydration-race"], + "source": [ + "#3358 §4 ('leaving it unticked on the strength of a label')", + "ADR-0059", + "examples/app-showcase/src/ui/actions/index.ts (maxSize declarations)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — codifies the #3358 refusal to tick from a label into the oracle itself", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "records-forms.import-wizard-encoding-and-hints", "title": "CSV import: GBK decode, required-field hint, legacy-fallback notice", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", "surface": "browser", + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { + "app": "showcase", + "requires": [ + "an importable showcase object (e.g. showcase_contact) and a GBK-encoded CSV fixture; the pinned unit suites carry their own byte fixtures" + ] + }, "steps": [ - "run the pinned unit suites (they feed real GBK bytes and assert the exact decode, the disabled-Next hint, and the visible degrade notice)", - "optionally spot-check the wizard visually past the native file picker" + "run the pinned unit suites (they feed real GBK bytes and assert the exact decode, the disabled-Next hint, and the visible degrade notice): pnpm --filter @object-ui/plugin-grid exec vitest run src/importParsers.test.ts src/__tests__/importMissingRequiredHint.test.tsx src/__tests__/importLegacyReferenceGuard.test.tsx", + "capture the suite output as the primary evidence (the native file-picker hand-off is deliberately not browser-automated)", + "optionally spot-check visually: open the import wizard on showcase_contact, load a GBK CSV past the native picker, and screenshot the decoded preview", + "in the spot-check, map columns while leaving a required field (name) unmapped; observe the Next control and its hint", + "verify the wizard's outcome server-side after a completed import: filtered GET /api/v1/data/showcase_contact for an imported marker row" ], "acceptance": [ { - "clause": "all three clauses are pinned by targeted tests in objectui", + "clause": "GBK bytes decode to the exact expected strings (no mojibake) — pinned", "oracle": "test", - "verify": "pnpm --filter @object-ui/plugin-grid exec vitest run src/importParsers.test.ts src/__tests__/importMissingRequiredHint.test.tsx src/__tests__/importLegacyReferenceGuard.test.tsx", + "verify": "pnpm --filter @object-ui/plugin-grid exec vitest run src/importParsers.test.ts", "evidence": "test run output" + }, + { + "clause": "an unmapped required field disables Next AND shows the named required-field hint — pinned", + "oracle": "test", + "verify": "pnpm --filter @object-ui/plugin-grid exec vitest run src/__tests__/importMissingRequiredHint.test.tsx", + "evidence": "test run output" + }, + { + "clause": "the legacy-reference fallback path shows a visible degrade notice instead of silently degrading — pinned", + "oracle": "test", + "verify": "pnpm --filter @object-ui/plugin-grid exec vitest run src/__tests__/importLegacyReferenceGuard.test.tsx", + "evidence": "test run output" + }, + { + "clause": "a completed import lands rows the API can read back (the wizard's end state is server rows, not a success toast)", + "oracle": "api", + "verify": "filtered GET for an imported marker row returns it with decoded values intact", + "evidence": "the read" } ], + "negative": [ + "a GBK file whose preview renders mojibake while the tests pass means the WIRED wizard regressed against the pinned parser — a FAIL, file against the wizard wiring", + "an import that reports success while the filtered API read finds no rows is a FAIL (silent drop)" + ], "automated": { "kind": "unit", "ref": "objectui: packages/plugin-grid/src/importParsers.test.ts (+ importMissingRequiredHint, importLegacyReferenceGuard)" }, - "traps": ["automation-input"], + "traps": ["automation-input", "stale-console-bundle"], "source": ["#3358 §4 (ticked on test evidence — the native file picker hand-off is deliberately not automated)"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358's test-evidence resolution", "ref": "#3358" }] + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358's test-evidence resolution", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] } ] } From 2b57510314a78b3ad961ccb120fe5e80a01adcbc Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 13:34:48 +0000 Subject: [PATCH 05/14] =?UTF-8?q?feat(qa):=20=E6=B7=B1=E5=8C=96=20platform?= =?UTF-8?q?-core=20=E4=B8=8E=20approvals=20=E5=9F=9F(=E6=A3=80=E6=9F=A5?= =?UTF-8?q?=E7=82=B9,80=20=E9=A1=B9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit platform-core: /ready probe, authored-nav-vs-served diff, new seed-integrity item (authored row baselines, verbatim value spot-diff, upsert idempotence — the #3408/#3415 silent-seed class as first-class FAILs), metadata registry serving + draft→publish round-trip with deny gates. approvals: per-group signoff now pins the exact seed wiring (seed-approval-demo.ts distinct holders, run resume down the approve edge, actor derived from session #3800), quorum keeps its fixture blocker plus a runnable clamp-contrast clause. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01YD9f6FYyMraUWYeJf53V43 --- .../platform-checklist/areas/api-backend.json | 459 ++++++++++++++-- .../platform-checklist/areas/approvals.json | 510 ++++++++++++++++-- .../areas/platform-core.json | 390 ++++++++++++-- 3 files changed, 1236 insertions(+), 123 deletions(-) diff --git a/docs/qa/platform-checklist/areas/api-backend.json b/docs/qa/platform-checklist/areas/api-backend.json index 4a02e51fca..efded2dfd9 100644 --- a/docs/qa/platform-checklist/areas/api-backend.json +++ b/docs/qa/platform-checklist/areas/api-backend.json @@ -5,105 +5,498 @@ "items": [ { "id": "api-backend.batch-transactional-discovery", - "title": "transactionalBatch capability bit matches the mounted /batch behavior", + "title": "transactionalBatch capability bit matches the mounted /batch behavior — atomicity, rollback codes, size cap", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "api", + "personas": ["admin (or any member entitled to the objects the batch touches)"], + "fixtures": { + "app": "showcase", + "requires": ["two batch-writable objects (showcase_private_note and showcase_task both accept baseline member creates)", "default maxBatchSize (200 unless RestServerConfig.batch overrides)"] + }, "steps": [ - "read client.capabilities.transactionalBatch (or the capabilities endpoint)", - "exercise /api/v1/batch: a well-formed atomic batch, then atomic:false" + "boot showcase isolated; GET /api/v1/discovery and read capabilities.transactionalBatch (also compare client.capabilities.transactionalBatch if driving via @objectstack/client)", + "POST /api/v1/batch with a well-formed cross-object atomic batch: operations = [create showcase_private_note {title}, create showcase_task {title}], options {\"atomic\": true}; verify both rows exist afterwards", + "POST /api/v1/batch with the same operations and options {\"atomic\": false}; capture status + error code", + "POST /api/v1/batch where a MIDDLE operation must fail (e.g. create showcase_invoice missing its required name), sandwiched between two valid creates; capture the per-operation results and re-read all three would-be rows", + "POST /api/v1/batch with operations.length > maxBatchSize (201 no-op creates on the default config); capture the rejection", + "POST /api/v1/data/showcase_private_note/batch (the PER-OBJECT batch door) with a mixed valid/invalid set and NO atomic flag; capture the per-row outcomes" ], "acceptance": [ { - "clause": "the discovery bit is true exactly when /batch is mounted and transaction-capable; atomic:false answers 400 BATCH_NOT_ATOMIC", + "clause": "the discovery bit is true exactly when /batch is mounted and transaction-capable — capability read and live behavior agree", + "oracle": "api", + "verify": "GET /api/v1/discovery capabilities.transactionalBatch == true AND the atomic batch in step 2 succeeds end-to-end (or, on a runtime without tx support, the bit is false and /batch refuses atomically-dependent use)", + "evidence": "discovery body + batch trace" + }, + { + "clause": "atomic:false on the CROSS-OBJECT /batch answers 400 BATCH_NOT_ATOMIC — the endpoint is all-or-nothing by construction (batch.zod.ts: atomic accepted for symmetry, MUST be true)", "oracle": "api", - "verify": "capability read + the two batch calls with their status/error codes", - "evidence": "the three responses" + "verify": "step-3 response: status 400, error code BATCH_NOT_ATOMIC, message pointing at POST /data/:object/batch for non-atomic per-object batches", + "evidence": "response" + }, + { + "clause": "a failing member rolls the whole atomic batch back with the #4793 per-row codes: rows before the failure report ROLLED_BACK (written then undone), rows after report NOT_ATTEMPTED (never ran) — and NO row from the batch persists", + "oracle": "api", + "verify": "step-4 per-operation results carry the two codes in the right positions; follow-up GETs find none of the three records", + "evidence": "batch response + the absent-row reads" + }, + { + "clause": "an oversize batch is refused up front with 400 BATCH_TOO_LARGE (the configured maxBatchSize, default 200) and creates nothing", + "oracle": "api", + "verify": "step-5 response: 400 + BATCH_TOO_LARGE; system-context count of the probe-titled rows is 0", + "evidence": "response + count" + }, + { + "clause": "the per-object /data/:object/batch door defaults to NON-atomic per-row outcomes (ADR-0119 D4: atomic defaults false there — the declared-but-unenforced default(true) was the defect): valid rows land, invalid rows report their error, neither blocks the other", + "oracle": "api", + "verify": "step-6 response has per-row success and error entries; re-reads confirm valid rows persisted", + "evidence": "response + re-reads" } ], - "source": ["#3358 §9"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }] + "negative": ["the two batch doors must not be conflated: atomic:false must NOT be accepted-and-ignored on /api/v1/batch (silent non-atomic acceptance is the pre-ADR-0119 bug shape), and the per-object door must NOT silently roll back valid rows when a sibling row fails without atomic being requested"], + "traps": ["dispatcher-vs-hono-route"], + "source": ["#3358 §9", "packages/spec/src/api/batch.zod.ts (ADR-0119 D4)", "packages/spec/src/api/error-code-ledger.zod.ts (BATCH_NOT_ATOMIC, BATCH_TOO_LARGE, NOT_ATTEMPTED, ROLLED_BACK — #4793)", "packages/rest/src/rest-route-ledger.ts (batch family)", "packages/spec/src/api/discovery.zod.ts (transactionalBatch)"], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "api-backend.formula-gates", "title": "Formula runtime fixes hold and date-arithmetic fails at build time", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "build", "steps": [ - "evaluate the fixed runtime shapes against built @objectstack/formula: due_date == today(), cond ? x : null, floor/ceil", - "run os build on a config carrying date arithmetic (end - start + 1)" + "build the workspace (or confirm dist freshness) so @objectstack/formula's BUILT output is what runs — the stale-dist trap is the known false-negative here", + "evaluate the fixed runtime shapes against the built package: due_date == today() (date equality against the macro), cond ? x : null (ternary with null arm), floor(x)/ceil(x)", + "author a scratch object config carrying a formula field with date arithmetic (end - start + 1) and run os build on it", + "run os build on the UNTOUCHED showcase app", + "capture the harness values, both build exit codes, and the located error text" ], "acceptance": [ { - "clause": "the runtime shapes evaluate correctly", + "clause": "the runtime shapes evaluate correctly against the BUILT package (not src)", "oracle": "test", - "verify": "harness run against the BUILT package (not src) returns the expected values", + "verify": "harness run returns the expected values for the three shapes; the harness imports from dist/node_modules, never src", "evidence": "harness output" }, { "clause": "date arithmetic is a build-time ERROR in os build — not a runtime surprise", "oracle": "build", - "verify": "os build exits non-zero with the located formula error", + "verify": "os build exits non-zero on the scratch config", + "evidence": "build output + exit code" + }, + { + "clause": "the build error is LOCATED and actionable: it names the object/field (or file) carrying the offending formula, not just a generic failure", + "oracle": "build", + "verify": "error text contains the scratch field's name/path", + "evidence": "error text" + }, + { + "clause": "the gate does not over-fire: os build on the stock showcase (which uses legitimate date comparisons like due_date == today()) exits 0", + "oracle": "build", + "verify": "untouched showcase os build exit code 0", "evidence": "build output" } ], + "negative": ["a green harness run that silently loaded src instead of dist is a false pass — capture the resolved module path as part of the evidence; if the path points into src/, the clause is not-run, not pass"], "traps": ["stale-dist"], "source": ["#3358 §9"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }] + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "api-backend.enforce-or-remove-authoring-gates", "title": "Removed/retired authoring keys fail at parse/build with located guidance", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "build", "steps": [ - "author a config using a retired key (per the ADR-0087 registries / retiredKey tombstones)", - "parse/build it" + "sample retired keys across the ADR-0087 registries, at minimum: a query-surface tombstone (QueryAST cursor / joins / distinct / windowFunctions — retiredKey per #4286), the error-envelope tombstone (EnhancedApiError.fieldErrors, renamed to fields by ADR-0114 D4), and one entry from the retired-filter-operator registry in packages/spec/src/data/filter.zod.ts (RetiredFilterOperatorGuidance)", + "author a config/payload using each sampled key: parse the query via the spec schema (QuerySchema.safeParse with cursor: {...}), parse an error envelope carrying fieldErrors, author an object using the retired filter operator; run ObjectSchema.create / os build where the key is config-side", + "capture each rejection's full text", + "author one key documented as silently-stripped-by-design (a pure display annotation) and parse it — the strip must be deliberate, documented behavior, not an accident", + "record which registry each sampled key came from" ], "acceptance": [ { - "clause": "the parse/build rejects the retired key with located guidance naming the replacement — never a silent strip for tombstoned keys", + "clause": "each tombstoned key is REJECTED at parse/build — never silently stripped (the retiredKey mechanism exists precisely because non-strict schemas would otherwise drop the key clean)", + "oracle": "build", + "verify": "safeParse fails / os build exits non-zero for every sampled tombstone", + "evidence": "the rejections" + }, + { + "clause": "every rejection carries LOCATED guidance naming the replacement: cursor → keyset where-predicate on the sort key, joins → expand, fieldErrors → fields (ADR-0114 D4 #3977), retired filter operator → its registry-declared successor", + "oracle": "build", + "verify": "each error text names the replacement spelling; a bare 'unknown key' with no prescription is a FAIL of the guidance contract", + "evidence": "error texts" + }, + { + "clause": "the sampled silently-stripped-by-design key still parses clean AND its documentation says so — the strip stays deliberate and documented, not accidental", + "oracle": "build", + "verify": "parse succeeds; cite the doc/schema comment declaring the strip", + "evidence": "parse result + doc cite" + }, + { + "clause": "the sample covers at least three distinct registries/surfaces (query schema, error envelope, filter operators) — one surface proving the mechanism does not prove the others wired it", "oracle": "build", - "verify": "ObjectSchema.create / os build throws the located error for a sampled retired key", - "evidence": "the error text" + "verify": "run record lists the sampled keys and their registry of origin", + "evidence": "run record" } ], - "negative": ["also sample one key documented as silently-stripped-by-design (e.g. pure display annotations) to confirm the strip stays deliberate and documented, not accidental"], - "source": ["#3358 §9", "ADR-0049", "ADR-0087"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }] + "negative": ["also sample one key documented as silently-stripped-by-design (e.g. pure display annotations) to confirm the strip stays deliberate and documented, not accidental — and a tombstoned key that parses CLEAN anywhere is a P1 FAIL (the silent fourth state ADR-0049/0078 forbids)"], + "source": ["#3358 §9", "ADR-0049", "ADR-0087", "#4286 (query tombstones)", "ADR-0114 D4 / #3977 (fieldErrors)", "packages/spec/src/shared/retired-key.ts", "packages/spec/src/data/filter.zod.ts (RetiredFilterOperatorGuidance)"], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "api-backend.server-timing-admin-gated", "title": "Server-Timing spans emit for admins only — on the server os dev actually runs", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", "surface": "api", - "personas": ["admin", "non-admin"], + "personas": ["admin", "non-admin member"], "steps": [ - "with OS_SERVER_TIMING enabled, request any route as admin with X-OS-Debug-Timing", - "repeat as a non-admin" + "start the REAL dev server (os dev) with OS_SERVER_TIMING enabled — never a simulated dispatch; the #3361 regression lived exactly in the dispatcher-vs-hono seam while unit tests stayed green", + "as admin: GET /api/v1/data/showcase_task with the X-OS-Debug-Timing request header; dump all response headers", + "as the non-admin member: the identical request; dump headers", + "restart the server WITHOUT OS_SERVER_TIMING and repeat the admin request", + "capture the three header dumps" ], "acceptance": [ { - "clause": "admin responses carry auth/db/hooks/serialize spans; non-admin responses carry none — verified against the hono server os dev runs, because the #3361 regression lived exactly in the dispatcher-vs-hono seam while unit tests simulated the call and stayed green", + "clause": "admin responses carry the Server-Timing spans (auth/db/hooks/serialize) on the live hono server", + "oracle": "network", + "verify": "step-2 response has a Server-Timing header naming the span set", + "evidence": "header dump" + }, + { + "clause": "non-admin responses carry NO spans — the gate keys on the caller's privilege, and the payload of the response is otherwise identical (timing must not leak through a side door)", + "oracle": "network", + "verify": "step-3 response lacks Server-Timing entirely", + "evidence": "header dump" + }, + { + "clause": "with the env flag off, even the admin gets no spans — the opt-in is the second gate, both sides verified", + "oracle": "network", + "verify": "step-4 response lacks Server-Timing", + "evidence": "header dump" + }, + { + "clause": "the oracle is the live server os dev runs (dispatcher-vs-hono seam), not a unit-level simulated dispatch", "oracle": "network", - "verify": "live response headers for both personas on the running dev server", - "evidence": "the two header dumps" + "verify": "evidence headers come from real HTTP responses against the running dev-server port recorded in the run env", + "evidence": "the raw traces incl. host:port" } ], + "negative": ["a run that only checks the admin side is at best partial: the non-admin absence is the security half of the item, and it must be measured on the same live server in the same run"], "traps": ["dispatcher-vs-hono-route", "wrong-persona"], "source": ["#3358 §9", "#3361"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; oracle pinned to the live server because of #3361", "ref": "#3358" }] + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; oracle pinned to the live server because of #3361", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "api-backend.query-contract-matrix", + "title": "Data-API query contract: every filter operator gives known answers; $-params, select, sort, expand honored; malformed input 400s with the exact code", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P0", + "surface": "api", + "personas": ["admin (baseline known-answer runs)", "contributor (the RLS/FLS-restricted persona for the expand enforcement clause)"], + "fixtures": { + "app": "showcase", + "requires": ["seeded showcase_account rows (Northwind, Contoso, Fabrikam, Stark Industries, 华宁科技, … with status ∈ {active, prospect, churned}, sales_region ∈ {amer, emea, apac}, numeric annual_revenue, date signed_on, churn_reason null except churned rows) — the known-answer dataset", "seeded showcase_task rows (status ∈ {backlog, todo, in_progress, in_review, done}, assignee emails, numeric progress/estimate_hours, project lookup)"] + }, + "steps": [ + "boot showcase isolated; as admin GET /api/v1/data/showcase_account with no filter and a generous $top — this unfiltered baseline is the local ground truth every operator answer is computed from (never hardcode counts; the seed can drift)", + "for each operator variant below, run BOTH spellings against showcase_account (or showcase_task where the type fits): the POST /api/v1/data/showcase_account/query body form (where: {field: {$op: value}}) and the AST array form ([field, op, value] — spellings per AST_OPERATOR_MAP); e.g. where {status: {$eq: 'active'}}, {annual_revenue: {$gt: 10000000}}, {status: {$in: ['active','prospect']}}, {annual_revenue: {$between: [5000000, 50000000]}}, {name: {$contains: 'orth'}}, {name: {$startsWith: 'Con'}}, {churn_reason: {$null: true}}", + "for each operator: compute the expected id set from the baseline locally and compare with the returned records — a known-answer check, not a smoke 200", + "pagination window: GET /api/v1/data/showcase_account?$orderby=name&$top=3&$skip=2 and verify it equals rows 3–5 of the locally sorted baseline; also POST body limit/offset/top equivalents", + "selection: ?$select=name,status (and body fields: ['name','status']) — response objects carry exactly the requested keys (plus system identity keys); dotted related column fields: ['account.name'] on showcase_invoice", + "sort: $orderby / orderBy asc and desc on annual_revenue — full ordering compared to the locally sorted baseline, not just first row", + "expand: as the CONTRIBUTOR (restricted persona), POST /api/v1/data/showcase_invoice/query with expand: {contact: {object: 'showcase_contact', fields: ['name']}} on an invoice they own — contributor holds NO showcase_contact grant, so the expanded row must be withheld/masked while their own invoice row returns (15.1 §A6: expand routes through the secured find path, #2850)", + "negative shapes: POST query where {status: {$nin: 'done'}} on showcase_task (scalar comparand for a collection operator — #5869) and the AST twin ['status','not_in','done']; GET with an unknown $-param (?$pageSize=5); GET with a bare unknown key (?not_a_field=x); sort on a nonexistent field; a where clause naming a nonexistent field", + "boundary: where {status: {$in: []}} (must match NOTHING) and {status: {$nin: []}} (must match EVERYTHING) — both documented-legitimate, never 400 (filter-comparand-shape.ts)" + ], + "acceptance": [ + { + "clause": "every operator variant returns exactly the locally-computed answer set from the seeded data, in both the $-object and AST spellings — set equality on ids, not count equality", + "oracle": "api", + "verify": "per-variant diff of returned ids vs the baseline-computed expectation; any extra OR missing row fails that variant", + "evidence": "per-variant diff table" + }, + { + "clause": "pagination is stable and windowed: $top/$skip (and limit/offset/top body keys) return the exact ordered slice of the baseline", + "oracle": "api", + "verify": "slice comparison against the locally sorted baseline for at least two windows", + "evidence": "the two window responses" + }, + { + "clause": "$select/fields narrows the payload to the requested columns (dotted related columns included) — no unrequested business fields leak", + "oracle": "api", + "verify": "key-set assertion on every returned record", + "evidence": "response bodies" + }, + { + "clause": "sort orders the FULL result both directions; INVALID_SORT (400) answers a sort on a nonexistent field", + "oracle": "api", + "verify": "full-sequence comparison asc+desc; then the bad-sort trace shows 400 + code INVALID_SORT", + "evidence": "ordered responses + rejection" + }, + { + "clause": "expand enforces the TARGET object's RLS/FLS on expanded rows (15.1 §A6): the restricted contributor's expand of showcase_contact returns their invoice row with the contact withheld/masked, never the foreign contact's fields", + "oracle": "api", + "verify": "contributor expand response: invoice present, contact expansion absent/null/masked; the SAME query as admin returns the contact (both sides)", + "evidence": "both personas' responses" + }, + { + "clause": "a scalar comparand on a collection operator answers 400 INVALID_FILTER naming the operator, the field, and the expected shape — never 500 DATABASE_ERROR (#5869, both doors: the $-object body AND the AST array spelling)", + "oracle": "api", + "verify": "both step-8 $nin probes: status 400, code INVALID_FILTER, message names the operator and field", + "evidence": "both rejections" + }, + { + "clause": "unknown inputs get their exact ledgered code: unknown $-param → 400 UNSUPPORTED_QUERY_PARAM (listing the supported $-params), unknown bare key or unknown where-field → 400 INVALID_FIELD (#4134 — a filter on a nonexistent field must NEVER answer 200 with rows, in either direction)", + "oracle": "api", + "verify": "the three traces show 400 + the named codes", + "evidence": "rejections" + }, + { + "clause": "empty-list boundary: $in: [] returns zero rows, $nin: [] returns the full visible set, both 200 — arity is not the gate's business, only list-ness", + "oracle": "api", + "verify": "the two responses vs the baseline", + "evidence": "responses" + }, + { + "clause": "every variant below carries a recorded verdict; operators not runnable against seeded types (none expected — the account/task fields cover text/number/date/select/null) are recorded skipped-with-reason, never silently omitted", + "oracle": "api", + "verify": "run record has one row per variant", + "evidence": "run record" + } + ], + "negative": ["silent WIDENING is the catastrophic failure mode (#3899): a malformed query body (e.g. {\"filter\": {…}} — not a QueryAST key) must answer 400 VALIDATION_FAILED, never degrade into an unfiltered 200 full read; verify by asserting the malformed-body probe's status AND that its response row count is not the full table"], + "variants": ["op:eq", "op:ne", "op:gt", "op:gte", "op:lt", "op:lte", "op:in", "op:not_in", "op:between", "op:contains", "op:not_contains", "op:starts_with", "op:ends_with", "op:like (driver-verbatim pattern, NOT auto-%-wrapped — canonicalAstOperator keeps it distinct)", "op:is_null", "op:is_not_null", "param:$top", "param:$skip", "param:$select", "param:$orderby", "param:$count", "param:$search", "param:$filter", "param:$expand"], + "automated": { "kind": "unit", "ref": "packages/objectql/src/engine.test.ts ([#2850] expand sub-read through the secured find path) + packages/objectql/src/filter-comparand-shape.ts (#5869 gate)" }, + "traps": ["seed-data-thin", "single-datapoint", "dispatcher-vs-hono-route"], + "source": ["packages/spec/src/data/filter.zod.ts (AST_OPERATOR_MAP / FieldOperatorsSchema — the operator variant source)", "packages/spec/src/data/query.zod.ts (QueryAST keys)", "packages/metadata-protocol/src/protocol.ts (supported $-params + UNSUPPORTED_QUERY_PARAM #2926 ⑩, INVALID_FIELD #4134)", "#5869 (commit 10c4ea9)", "release-15.1 plan §A6", "examples/app-showcase/src/data/seed/index.ts"], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new — query-contract matrix over the spec operator vocabulary with known-answer checks, per the deep-test contract", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "api-backend.error-envelope-ledger", + "title": "Sampled endpoints return the standard error envelope with ledgered codes — no invented codes, no retired keys, statuses match the map", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": ["admin", "a baseline member (for the 403 sample)", "anonymous (for the 401 sample)"], + "fixtures": { + "app": "showcase", + "requires": ["showcase_invoice.name is required:true — the cheapest deterministic VALIDATION_FAILED", "a member persona lacking showcase_announcement create (member_default: create false) — the deterministic 403"] + }, + "steps": [ + "boot showcase isolated; induce one error per family and capture the FULL body + status each time:", + "validation: as admin POST /api/v1/data/showcase_invoice {} (missing required name) — expect the field-level envelope", + "auth: anonymous GET /api/v1/data/showcase_private_note — the 401 sample", + "authorization: as the baseline member POST /api/v1/data/showcase_announcement {\"title\": \"probe\"} — the 403 sample", + "not-found: GET /api/v1/data/not_a_real_object and GET /api/v1/data/showcase_task/nonexistent-id-0000 — the two 404 flavors", + "batch: POST /api/v1/batch with options {\"atomic\": false} — the registered-extension-code sample (BATCH_NOT_ATOMIC)", + "query-rejection: GET /api/v1/data/showcase_account?$pageSize=5 — the UNSUPPORTED_QUERY_PARAM sample", + "validate every captured body against the spec: code membership in StandardErrorCode ∪ ERROR_CODE_LEDGER, envelope shape per ErrorResponseSchema/EnhancedApiErrorSchema (noting the REST flat dialect where it is the declared shape), fields[] entries against FieldErrorCode" + ], + "acceptance": [ + { + "clause": "every sampled error code is LEDGERED: code ∈ StandardErrorCode (errors.zod.ts) ∪ ERROR_CODE_LEDGER (error-code-ledger.zod.ts) — an unregistered code is a FAIL per ADR-0112 D3/D4 (no silent fourth state)", + "oracle": "api", + "verify": "for each captured body, membership check of error code against the two spec files", + "evidence": "the captured bodies + the membership table" + }, + { + "clause": "the validation sample carries the field-level array under `fields` with codes from the FIELD-level catalog (lowercase snake, ADR-0114 D2): missing name → fields[] entry {field: 'name', code: 'required'} with a localized message and label", + "oracle": "api", + "verify": "step-2 body: top-level code VALIDATION_FAILED, fields[0].field == 'name', fields[0].code == 'required'", + "evidence": "the body" + }, + { + "clause": "the retired `fieldErrors` key NEVER appears in any sampled body (ADR-0114 D4 tombstone — producers emit `fields`)", + "oracle": "api", + "verify": "key-absence assertion across all captured bodies", + "evidence": "the bodies" + }, + { + "clause": "HTTP status matches the code's declared mapping (HttpStatusErrorCodeMap): 400 validation/filter/param codes, 401 UNAUTHENTICATED, 403 PERMISSION_DENIED, 404 not-found flavors", + "oracle": "api", + "verify": "status-vs-code table across the samples; any mismatch (e.g. a 500 carrying a caller-fixable code) fails", + "evidence": "the table" + }, + { + "clause": "the two 404 flavors are distinguishable in the body (unknown OBJECT vs unknown RECORD) so a client can tell schema drift from data absence", + "oracle": "api", + "verify": "compare the two step-5 bodies: different messages/codes identifying object-level vs record-level not-found", + "evidence": "both bodies" + }, + { + "clause": "one sample per family variant below is captured — a family not sampled leaves the item partial", + "oracle": "api", + "verify": "run record carries one verdict per family", + "evidence": "run record" + } + ], + "negative": ["any sampled failure answering 500 INTERNAL/UNCLASSIFIED for input the CALLER can fix is a FAIL of this item (the #5869 class — a server-fault code for a client mistake); likewise a 2xx on any of the induced-error probes is a FAIL (silent success)"], + "variants": ["family:validation", "family:auth-401", "family:authz-403", "family:not-found-object", "family:not-found-record", "family:batch-extension-code", "family:query-rejection"], + "traps": ["dispatcher-vs-hono-route"], + "source": ["packages/spec/src/api/error-code-ledger.zod.ts (ADR-0112 D3)", "packages/spec/src/api/errors.zod.ts (StandardErrorCode, FieldErrorCode, HttpStatusErrorCodeMap)", "packages/spec/src/shared/error-map.zod.ts", "packages/rest/src/rest-route-ledger.ts (sampled families)", "ADR-0114 D2/D4"], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new — error-envelope conformance sampling grounded in the two-tier code ledger, per the deep-test contract", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "api-backend.bulk-write-contract", + "title": "Bulk write doors (createMany/updateMany/deleteMany, per-object batch): per-row outcomes, size cap, single/bulk parity, no silent full-table writes", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": ["a baseline member (bulk writes on showcase_private_note — also exercises the owner-scoping of #2982)", "admin (cap and parity runs)"], + "fixtures": { + "app": "showcase", + "requires": ["showcase_private_note (baseline create/edit — the bulk probe object)", "default maxBatchSize 200 unless overridden"] + }, + "steps": [ + "boot showcase isolated; as the member POST /api/v1/data/showcase_private_note/createMany with records: [3 notes with unique-marker titles]; GET them back", + "single/bulk parity: POST a 4th note via the single door (POST /api/v1/data/showcase_private_note) and diff its response/stored shape against one createMany row (same fields stamped: owner_id, timestamps, defaults)", + "as the member POST /api/v1/data/showcase_private_note/updateMany with a where clause matching ONLY the marker titles and a body change; re-read", + "as the member POST updateMany with a where clause matching NOTHING (an impossible marker); capture the response", + "as admin POST createMany with records.length == maxBatchSize+1 (201 on default config); capture", + "as the member POST deleteMany scoped to the marker titles; re-read; then re-issue the same deleteMany (now matching nothing)", + "throughout: capture per-row result arrays and any ERR_BULK_RESULT_MISMATCH appearance" + ], + "acceptance": [ + { + "clause": "createMany lands every row with the same server-side stamping as the single door: owner_id auto-stamped to the caller, defaults applied — single/bulk parity on the stored shape", + "oracle": "api", + "verify": "field-by-field diff of a createMany row vs the single-door row (ignoring ids/timestamps values, comparing key sets and stamped semantics)", + "evidence": "the reads + diff" + }, + { + "clause": "updateMany applies exactly to the where-matched set: marker rows changed, every other row untouched (spot-check via a system-context read of a non-marker note)", + "oracle": "api", + "verify": "re-reads: all marker rows carry the change; the control row does not", + "evidence": "re-reads" + }, + { + "clause": "an empty match is a calm no-op: updateMany/deleteMany matching nothing answer 2xx reporting 0 affected — NOT an error, and NEVER a full-table write (the #2982 failure shape was bulk writes escaping their scope)", + "oracle": "api", + "verify": "step-4/6 responses report zero affected; a follow-up unfiltered system read shows no unexpected mutations/deletions", + "evidence": "responses + control read" + }, + { + "clause": "the size cap holds on the bulk doors: maxBatchSize+1 records answer 400 BATCH_TOO_LARGE and create nothing", + "oracle": "api", + "verify": "step-5 response 400 + BATCH_TOO_LARGE; count of that batch's marker rows is 0", + "evidence": "response + count" + }, + { + "clause": "deleteMany removes exactly the scoped set and the deletion persists", + "oracle": "api", + "verify": "post-delete GET finds zero marker rows; the member's other notes remain", + "evidence": "re-reads" + }, + { + "clause": "ERR_BULK_RESULT_MISMATCH never surfaces on these healthy paths — its appearance means the engine's reported outcome diverged from the driver's actual writes and is an immediate FAIL with the trace attached", + "oracle": "api", + "verify": "grep the captured responses (and server log) for ERR_BULK_RESULT_MISMATCH", + "evidence": "responses + log excerpt" + } + ], + "negative": ["run the updateMany-matching-nothing probe as a persona with rows it CANNOT see and confirm the invisible rows are not counted or mutated (bulk writes are RLS-scoped, #2982) — a nonzero affected-count there is a security FAIL, not a bookkeeping quirk"], + "variants": ["door:createMany", "door:updateMany", "door:deleteMany", "door:per-object-batch (POST /data/:object/batch)"], + "automated": { "kind": "dogfood", "ref": "packages/qa/dogfood/test/owner-anchor-and-bulk-writes.dogfood.test.ts (bulk halves)" }, + "traps": ["wrong-persona"], + "source": ["packages/rest/src/rest-route-ledger.ts (batch family — the four doors)", "packages/spec/src/api/batch.zod.ts", "packages/spec/src/api/error-code-ledger.zod.ts (BATCH_TOO_LARGE, ERR_BULK_RESULT_MISMATCH)", "#2982", "release-15.1 plan §A3/A4"], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new — bulk-write contract item (boundaries, parity, per-row outcomes), per the deep-test contract", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "api-backend.route-ledger-live-parity", + "title": "Every REST_ROUTE_LEDGER family is actually mounted on the live server — no route that exists only in unit tests", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "api", + "personas": ["admin (authenticated, so a 401 cannot mask a 404)"], + "fixtures": { + "app": "showcase", + "requires": ["packages/rest/src/rest-route-ledger.ts — the audited route inventory (route-manager + direct-mount rows, full wire paths at /api/v1)"] + }, + "steps": [ + "boot the real HTTP stack (os dev); read REST_ROUTE_LEDGER and pick at least one representative route per family (discovery, openapi, metadata, ui, crud, batch, data-actions, forms, search, security, security-explain, record-shares, sharing-rules, reports, approvals, email, analytics, packages, external-datasource)", + "fire each sampled route as admin with a minimal-valid shape (GETs verbatim; parameterized routes filled with real seeded names, e.g. GET /api/v1/data/showcase_task, GET /api/v1/meta/object/showcase_task, GET /api/v1/security/explain)", + "capture status + code per route", + "fire one deliberately-unmounted path (GET /api/v1/definitely-not-a-route) as the 404 control", + "compare GET /api/v1/discovery capability bits against the families that answered (search/export/transactionalBatch at minimum)" + ], + "acceptance": [ + { + "clause": "every sampled ledger route answers something OTHER than a routing 404 on the live server — 2xx, or a structured 4xx/5xx from the handler (401/403/400/503 all prove the route is mounted); the #3361 lesson: unit-level dispatch proves nothing about the hono server", + "oracle": "api", + "verify": "per-route status table; any ledgered route answering the same not-found shape as the 404 control is a FAIL (mounted-in-tests-only)", + "evidence": "the status table + control trace" + }, + { + "clause": "the 404 control behaves as a control: the unmounted path answers the routing not-found shape, so the per-route comparison is meaningful", + "oracle": "api", + "verify": "control trace shows the distinct routing-404 body", + "evidence": "control trace" + }, + { + "clause": "openapi.json answers its DECLARED envelope either way: 200 with an OpenAPI 3.1 document, or 503 OPENAPI_UNAVAILABLE when no spec is bundled — never a raw 404", + "oracle": "api", + "verify": "GET /api/v1/openapi.json status ∈ {200, 503} with the ledgered code on 503", + "evidence": "trace" + }, + { + "clause": "discovery capability bits agree with live behavior for the sampled capabilities (a bit true ⇒ the family answers; a bit false ⇒ the family refuses coherently)", + "oracle": "api", + "verify": "cross-check the discovery body against the sampled families' answers", + "evidence": "discovery body + table" + }, + { + "clause": "direct-mount rows (package-routes, external-datasource-routes — the registrars that bypass RouteManager) are sampled too, since their registration path is exactly the one route-manager enumeration misses", + "oracle": "api", + "verify": "at least one direct-mount route in the sample set answers non-routing-404", + "evidence": "trace" + } + ], + "negative": ["do not tick this from RestServer.getRoutes() output or unit tests — the item exists because that oracle lied (#3361, dispatcher-vs-hono-route); the only admissible evidence is live HTTP traces from the running server"], + "variants": ["discovery", "openapi", "metadata", "ui", "crud", "batch", "data-actions", "forms", "search", "security", "security-explain", "record-shares", "sharing-rules", "reports", "approvals", "email", "analytics", "packages (direct-mount)", "external-datasource (direct-mount)"], + "traps": ["dispatcher-vs-hono-route"], + "source": ["packages/rest/src/rest-route-ledger.ts (variant source — the 19 families)", "packages/rest/src/rest-route-ledger.conformance.test.ts", "#3587", "#3361"], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new — live-mount parity sweep over the audited route ledger, per the deep-test contract", "ref": "claude/platform-test-checklist-ocwugl" } + ] } ] } diff --git a/docs/qa/platform-checklist/areas/approvals.json b/docs/qa/platform-checklist/areas/approvals.json index 4d958dd9e6..3f89ba72bf 100644 --- a/docs/qa/platform-checklist/areas/approvals.json +++ b/docs/qa/platform-checklist/areas/approvals.json @@ -8,48 +8,90 @@ "title": "Per-group sign-off (会签) needs one approval from EACH group", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "browser", "personas": ["approver holding exactly one group (e.g. manager)", "second approver holding the other group (e.g. finance/auditor)"], "fixtures": { "app": "showcase", "requires": [ - "a launched per_group request whose two groups resolve to two DISTINCT users (showcase: ExpenseSignoffFlow on EXP-2001, Ada Auditor holds auditor — seeded since #3409)" + "a launched per_group request whose two groups resolve to two DISTINCT users (showcase: ExpenseSignoffFlow on EXP-2001, Ada Auditor holds auditor — seeded since #3409)", + "seed-approval-demo.ts wiring: dev admin holds manager/finance/legal/exec but NOT auditor, so the manager group resolves to the admin and the finance group to Ada Auditor only; EXP-2001 ($1,500) sits under the $5,000 committee threshold so the quorum flow does not also open on it; submitter is Mei Phone (usr_showcase_phone_demo)" + ], + "knownGaps": [ + "Ada Auditor exists as a routable sys_user row only — better-auth sign-in for her needs an account provisioned at runtime (seed-approval-demo.ts: 'sign-in still needs a better-auth account'); the finance-group decision therefore needs either a provisioned Ada account or the server-granted admin override (can_override) — the run record must state which path was used" ] }, "steps": [ - "as the single-group approver, open the request in the approval inbox", - "record the pending slate and per-group progress from the drawer", - "approve, then re-read the request state over the API" + "boot showcase isolated (dogfood §0); sign in as the dev admin (the manager-group holder; NOT in the finance group of this node)", + "GET /api/v1/approvals/requests?status=pending and locate the EXP-2001 request opened by showcase_expense_signoff's per_group node (groups manager + finance)", + "GET /api/v1/approvals/requests/:id — record status, pending_approvers, pending_approver_names, pending_approver_groups, and the per-group tallies", + "open the inbox at /system/approvals (待我审批 tab); open the request drawer; screenshot the server-computed group chips", + "approve via the drawer dialog (ref-targeted click; fill the comment field); capture the decision POST /api/v1/approvals/requests/:id/approve", + "re-read the request via API; then complete the finance group's decision (as Ada, or via the documented override path) and re-read again", + "GET /api/v1/approvals/requests/:id/actions for the full decision timeline", + "GET /api/v1/automation/showcase_expense_signoff/runs/:runId — the parked flow run before and after finalization" ], "acceptance": [ { - "clause": "one group's approval satisfies that group and drops it from the pending slate, but the request stays pending on the remaining group", + "clause": "before any decision: status=pending, BOTH groups at 0/1, pending list names two DISTINCT user ids (admin for manager, Ada for finance)", + "oracle": "api", + "verify": "GET the request; assert status + per-group tallies + two distinct entries in pending_approvers with pending_approver_groups mapping them to manager / finance", + "evidence": "the initial request read" + }, + { + "clause": "after the manager-group approval: that group is satisfied and dropped from the slate, the request STILL pending on the finance group only", "oracle": "api", - "verify": "before: status=pending, both groups 0/1; after the approve: status STILL pending, approver's group satisfied, other group's user still listed as pending", - "evidence": "before/after API reads of the request (status + pending approvers)" + "verify": "re-read: status=pending; pending_approvers contains only the finance holder; the manager group's tally reads satisfied", + "evidence": "before/after request reads" }, { - "clause": "per-group progress is server-computed, not a client guess", + "clause": "the drawer's group chips are server-computed and match the API at each stage", "oracle": "screenshot", - "verify": "the drawer renders per-group tick badges ('N of M groups', group chips with counts) that match the API state", - "evidence": "drawer screenshot alongside the API read" + "verify": "chip screenshot at each stage alongside the API read — chips are keyed by (name, group) from pending_approver_groups (#2762), not a client recount", + "evidence": "screenshots + paired reads" + }, + { + "clause": "after the finance-group approval: the request finalizes approved and the flow run resumes down its approve edge to the Approved end", + "oracle": "api", + "verify": "request status=approved; the showcase_expense_signoff run transitions paused→completed with the approve branch taken", + "evidence": "final request read + run read" + }, + { + "clause": "the decision timeline carries one approval action per group with distinct actors and round-tripped comments", + "oracle": "api", + "verify": "GET /:id/actions: two approve rows, two distinct actor ids, each carrying the comment submitted in its dialog", + "evidence": "actions read" + }, + { + "clause": "the server derives the decision actor from the session — the posted actorId is a hint, not an authority (#3800)", + "oracle": "network", + "verify": "capture the approve POST; the recorded action row's actor is the authenticated session user regardless of any actorId in the body", + "evidence": "decision POST + action row" } ], "negative": [ - "contrast case: a request whose slots all resolve to ONE user finalizes on a single decision — confirm per_group did NOT (that contrast is the proof the behavior differs)" + "contrast case: a request whose slots all resolve to ONE user finalizes on a single decision — confirm per_group did NOT (that contrast is the proof the behavior differs)", + "a user in neither group POSTing /api/v1/approvals/requests/:id/approve directly must get a server-side FORBIDDEN — an approval recorded for a non-member is a FAIL even if the UI never offered the button" ], - "traps": ["automation-input"], - "source": ["#3358 §1", "#3409", "#3411"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the #3358 evidence run (decisive oracle: group drops but request stays pending)", "ref": "#3358" }] + "traps": ["automation-input", "wrong-persona"], + "source": [ + "#3358 §1", "#3409", "#3411", + "examples/app-showcase/src/automation/flows/index.ts (ExpenseSignoffFlow, behavior per_group)", + "examples/app-showcase/src/security/seed-approval-demo.ts (distinct-holder wiring + submitter stamping)", + "packages/spec/src/automation/approval.zod.ts (behavior enum + group labels, #3266)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from the #3358 evidence run (decisive oracle: group drops but request stays pending)", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "approvals.quorum-m-of-n", "title": "M-of-N quorum approves at the threshold; one rejection vetoes", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "browser", "personas": ["three DISTINCT users each holding one of the approver positions"], @@ -62,44 +104,88 @@ }, "blocked": { "by": "fixture", "ref": "#3358 (quorum slate collapses onto the admin — showcase design call pending)" }, "steps": [ - "as approver 1, approve — confirm the request stays pending (1 of 2)", - "as approver 2, approve — confirm the request finalizes and remaining tasks close", - "on a second request: as any approver, reject — confirm immediate veto" + "boot showcase isolated (dogfood §0); sign in as the dev admin", + "runnable today (the clamp contrast): GET the seeded showcase_committee_quorum request on EXP-DEMO (behavior quorum, minApprovals 2 over manager/finance/legal — all resolving to the admin); record the collapsed pending slate", + "approve it ONCE as the admin and re-read: it finalizes immediately — the documented runtime clamp (minApprovals can never exceed the resolvable approver count), recorded as the CONTRAST, never as M-of-N proof", + "once the three-distinct-users fixture exists: as approver 1, POST /api/v1/approvals/requests/:id/approve — re-read: status STILL pending (1 of 2), approver 1 dropped from the slate", + "as approver 2, approve — re-read: status=approved, remaining pending task for approver 3 closed", + "GET /:id/actions — exactly two approve rows from two distinct actors", + "on a SECOND quorum request: as any single approver, POST /reject — re-read immediately", + "screenshot the drawer's M-of-N progress at each stage alongside the API reads" ], "acceptance": [ { - "clause": "the request approves exactly when minApprovals distinct approvals are recorded", + "clause": "runnable today — clamp contrast: the stock EXP-DEMO slate collapses to the single admin and ONE approval finalizes the request (this is the clamp working, and the proof the fixture gap is real)", "oracle": "api", - "verify": "status transitions pending→pending→approved across the two approvals; approval action rows count 2 distinct actors", + "verify": "initial read shows a single-user pending slate despite three declared position approvers; one approve flips status to approved; the actions read shows exactly one approval row", + "evidence": "before/after reads + actions read" + }, + { + "clause": "the request approves exactly when minApprovals DISTINCT approvals are recorded — not before, not after", + "oracle": "api", + "verify": "status transitions pending→pending→approved across the two approvals; approval action rows count 2 distinct actors; after approval 1 the slate lists the two remaining holders", "evidence": "API reads after each decision" }, { "clause": "a single rejection vetoes even with quorum-1 approvals already recorded", "oracle": "api", - "verify": "status flips to rejected on the first reject; no further tasks remain actionable", - "evidence": "API read after the reject" + "verify": "on the second request: status flips to rejected on the first reject; no further tasks remain actionable; the flow run resumes down its reject edge", + "evidence": "API read after the reject + run read" + }, + { + "clause": "the third approver's pending task is closed by finalization, not left dangling", + "oracle": "api", + "verify": "after quorum is met, the request no longer lists approver 3 in pending_approvers and their inbox 待我审批 count drops", + "evidence": "request read + inbox count" + }, + { + "clause": "the drawer's M-of-N tally is server-computed and matches the API at each stage", + "oracle": "screenshot", + "verify": "progress screenshot after each decision alongside the paired API read", + "evidence": "screenshots + reads" } ], - "traps": ["seed-data-thin"], - "source": ["#3358 §1"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; carried the fixture blocker forward explicitly", "ref": "#3358" }] + "negative": [ + "a second approval by the SAME user must not count twice toward the quorum — a request that finalizes on two approvals from one actor is a FAIL of the distinctness the tally claims", + "the clamp contrast must be filed as evidence of the fixture gap, never ticked as M-of-N passing — a run record marking this item pass on stock seeds is itself a FAIL of protocol" + ], + "traps": ["seed-data-thin", "wrong-persona"], + "source": [ + "#3358 §1", + "examples/app-showcase/src/automation/flows/index.ts (CommitteeQuorumFlow, #3266)", + "packages/spec/src/automation/approval.zod.ts (behavior 'quorum' + minApprovals clamp: 'Clamped at runtime so it can never exceed the resolvable approver count')", + "examples/app-showcase/src/security/seed-approval-demo.ts (EXP-DEMO launch + admin position grants)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; carried the fixture blocker forward explicitly", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "approvals.inbox-metadata-actions", "title": "Inbox actions are metadata-driven and gated by the viewer's relationship", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "browser", "personas": ["approver who is NOT the submitter", "same user viewing a request they submitted"], "fixtures": { "app": "showcase", - "requires": ["requests with a real (non-null) submitter — seeded since #3411 (invoice submitted by admin, others by a no-position persona)"] + "requires": [ + "requests with a real (non-null) submitter — seeded since #3411 (invoice submitted by admin, others by a no-position persona)", + "seed-approval-demo.ts: the invoice dual sign-off request is submitted BY the admin (their own request, so 我发起的 is non-empty), while EXP-2001 / EXP-DEMO are submitted by Mei Phone (other-submitter requests the admin approves)" + ] }, "steps": [ - "as the same viewer, open one request submitted by someone else and one they submitted themselves", - "record the rendered action set on each" + "boot showcase isolated (dogfood §0); sign in as the dev admin", + "other-submitter case: open the EXP-DEMO request from the 待我审批 tab of /system/approvals; screenshot, then read the rendered action set from the drawer DOM", + "own-request case: open the invoice request from the 我发起的 tab; screenshot, then read its action set", + "GET /api/v1/approvals/requests/:id for both and record the server-computed viewer flags (can_act, is_submitter) and status", + "cross-check each rendered action against the declared metadata actions on sys_approval_request (visibility expressions gate on record.viewer.can_act / record.viewer.is_submitter / record.status)", + "spot-check an approver action: approve EXP-DEMO from the drawer; capture the POST and re-read the request", + "spot-check a submitter action: send a reminder on the invoice request; capture POST /api/v1/approvals/requests/:id/remind", + "GET /:id/actions on both requests and record the appended timeline rows" ], "acceptance": [ { @@ -115,70 +201,388 @@ "evidence": "side-by-side action lists for the two requests" }, { - "clause": "each action executes its REST route (spot-check one: the decision commits)", + "clause": "the rendered gating mirrors the server's viewer flags, which are computed on the request read — not a client heuristic", + "oracle": "api", + "verify": "for both requests, the API's viewer flags (can_act / is_submitter) predict exactly which action groups rendered; declared visibility expressions on sys_approval_request gate on those flags plus status", + "evidence": "request reads + the rendered sets" + }, + { + "clause": "each spot-checked action executes its REST route and the state change round-trips", "oracle": "network", - "verify": "clicking an action issues its API call and the state change round-trips", - "evidence": "network trace of the action call" + "verify": "the approve click POSTs /api/v1/approvals/requests/:id/approve and the re-read reflects the decision; the remind click POSTs /:id/remind and stays status=pending", + "evidence": "network traces + re-reads" + }, + { + "clause": "every executed action appends a timeline row naming actor and action kind", + "oracle": "api", + "verify": "GET /:id/actions before/after each spot-check: exactly one new row per action, with the acting user", + "evidence": "before/after actions reads" } ], + "negative": [ + "an action button rendered for a viewer whose server flags deny it (can_act=false rendering approve) is a FAIL even if the click would 403 — the declared visibility is the contract being tested, not the eventual rejection" + ], "traps": ["automation-input", "hydration-race"], - "source": ["#3358 §1", "#3411"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the #3358 evidence run (7-action table proven after #3411 stamped real submitters)", "ref": "#3358" }] + "source": [ + "#3358 §1", "#3411", + "packages/plugins/plugin-approvals/src/sys-approval-request.object.ts (declared actions + viewer-flag visibility expressions)", + "packages/rest/src/rest-route-ledger.ts (the approvals action route family)", + "objectui apps/console/src/pages/system/ApprovalsInboxPage.tsx (server-declared actions rendered; 待我审批 / 我发起的 tabs)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from the #3358 evidence run (7-action table proven after #3411 stamped real submitters)", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "approvals.viewer-gating-submitter-side", "title": "A submitter who is not an approver sees no approver buttons", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "browser", "personas": ["submitter holding NO approver position on their own pending request"], "fixtures": { "app": "showcase", "requires": ["one pending request routed to a position its submitter does not hold"], - "knownGaps": ["stock seeds route every request to positions the admin holds, so the admin is an approver on all of them (#3358) — needs one request addressed away from the signed-in persona"] + "knownGaps": [ + "stock seeds route every request to positions the admin holds, so the admin is an approver on all of them (#3358) — needs one request addressed away from the signed-in persona", + "the natural persona exists since #3411 — Mei Phone (usr_showcase_phone_demo) submits EXP-2001/EXP-DEMO and holds no approver position — but she is a sys_user row only: signing in as her needs a better-auth account provisioned at runtime (seed-approval-demo.ts)" + ] }, "blocked": { "by": "fixture", "ref": "#3358 (needs a request routed to a position the viewing submitter does not hold)" }, "steps": [ - "sign in as the no-position submitter", - "open their own pending request from the '我发起的' tab" + "provision a sign-in account for the no-position submitter persona (Mei Phone) or an equivalent fixture user; sign in as that persona", + "open /system/approvals — the 我发起的 tab must list her pending EXP-2001 request", + "open the request drawer; screenshot the rendered action set", + "GET /api/v1/approvals/requests/:id as this persona and record the viewer flags (can_act, is_submitter) and pending_approvers", + "forge the gate: as this persona POST /api/v1/approvals/requests/:id/approve directly with an approve body", + "re-read the request and its /actions timeline after the forged call" ], "acceptance": [ { - "clause": "approve/reject buttons do not render for the submitter, and the gate is server-side, not a client heuristic", + "clause": "the drawer omits approve/reject for the submitter while still rendering the submitter-side affordances (remind / recall)", + "oracle": "screenshot", + "verify": "drawer screenshot as the submitter: no approver decision buttons; remind/recall present on the own-request", + "evidence": "drawer screenshot" + }, + { + "clause": "the server's viewer flags say why: can_act=false, is_submitter=true — the rendered absence is metadata, not a client guess", + "oracle": "api", + "verify": "the request read as this persona carries can_act=false and is_submitter=true, and pending_approvers does not contain her id", + "evidence": "request read" + }, + { + "clause": "the gate is server-side: a forged direct POST of the decision route as the submitter is rejected (ADR-0057 D10 — UI absence alone is a client courtesy)", "oracle": "api", - "verify": "the drawer omits approver actions AND a forged direct POST of the decision route as the submitter is rejected (test both sides of the gate)", - "evidence": "screenshot of the drawer + the rejected API call" + "verify": "the forged approve answers FORBIDDEN (403-mapped); test BOTH sides — the entitled approver's decision on the same request succeeds", + "evidence": "the rejected call + the entitled approver's accepted call" + }, + { + "clause": "the forged call left no trace: status, tallies, and the actions timeline are byte-identical after it", + "oracle": "api", + "verify": "before/after reads of the request and /:id/actions — no new action row, no tally movement", + "evidence": "before/after reads" } ], - "traps": ["hydration-race"], - "source": ["#3358 §1", "ADR-0057 D10 (server is the authoritative visibility gate)"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; upgraded the oracle from DOM-only to both-sides (UI absence + server rejection)", "ref": "#3358" }] + "negative": [ + "a forged submitter decision that answers 2xx, appends an action row, or moves a tally is a FAIL — silent server acceptance is the exact failure this gate exists to prevent" + ], + "traps": ["hydration-race", "wrong-persona"], + "source": [ + "#3358 §1", + "ADR-0057 D10 (server is the authoritative visibility gate)", + "examples/app-showcase/src/security/seed-approval-demo.ts (Mei Phone: 'a clean submitter — a requester who is never also one of her own approvers')" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; upgraded the oracle from DOM-only to both-sides (UI absence + server rejection)", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "approvals.notification-deep-link", "title": "An approval notification deep-links straight into the request drawer", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", "surface": "browser", "personas": ["any pending approver"], + "fixtures": { + "app": "showcase", + "requires": ["at least one pending request whose open notified the signed-in approver (the three seeded demo requests suffice)"] + }, "steps": [ - "click an approval bell notification (or open the ?request= URL it carries) on a fresh page load" + "boot showcase isolated (dogfood §0); sign in as the dev admin (a pending approver on the seeded requests)", + "read the bell notification for a pending approval and record its actionUrl — it must carry /system/approvals?request= (#2678 P1.5)", + "cold-load that exact URL in a fresh page (no prior navigation); wait for settle; screenshot with the URL visible", + "GET /api/v1/approvals/requests/:id and cross-check the drawer's request identity and status against the read", + "repeat the cold load a second time on a fresh page (the hydration-race counter)", + "negative probe: cold-load /system/approvals?request= and screenshot the result" ], "acceptance": [ { - "clause": "the ?request= URL opens the request drawer directly on a cold load", + "clause": "the notification's actionUrl carries the ?request= deep link for the exact pending request", + "oracle": "api", + "verify": "read the notification/inbox row: its actionUrl contains /system/approvals?request= with the request id that the approvals API lists as pending for this user", + "evidence": "notification row read" + }, + { + "clause": "the ?request= URL opens the request drawer directly on a cold load — verified twice on fresh loads", "oracle": "screenshot", - "verify": "fresh navigation to .../approvals?request= renders the drawer for that exact request (verify twice on fresh loads)", - "evidence": "screenshot with the URL visible" + "verify": "fresh navigation renders the drawer for that exact request both times (verify twice on fresh loads)", + "evidence": "two screenshots with the URL visible" + }, + { + "clause": "the drawer shows the SAME request the API returns for that id — identity, status, pending slate", + "oracle": "api", + "verify": "field-match the drawer against GET /api/v1/approvals/requests/:id — a drawer that opened on the wrong request also 'renders a drawer'", + "evidence": "request read + drawer screenshot" + }, + { + "clause": "an unknown ?request id degrades to the inbox without a drawer (or an explicit not-found state) — never someone else's request", + "oracle": "screenshot", + "verify": "the nonexistent-id load renders the inbox list with no drawer or a not-found notice; assert no drawer carrying a different request id", + "evidence": "screenshot of the degraded state" } ], - "traps": ["hydration-race"], - "source": ["#3358 §1"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358 (verified twice on fresh loads)", "ref": "#3358" }] + "negative": [ + "a deep link that lands on the inbox with the WRONG request (or none) selected while the run records 'drawer rendered' is a FAIL — the clause is the exact request id, not the drawer's existence" + ], + "traps": ["hydration-race", "shared-browser-tab"], + "source": [ + "#3358 §1", + "objectui apps/console/src/pages/system/ApprovalsInboxPage.tsx (#2678 P1.5 — 'notifications carry /system/approvals?request=')" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358 (verified twice on fresh loads)", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "approvals.decision-action-matrix", + "title": "Every approval action executes its REST route and produces the expected state transition plus a timeline entry", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["dev admin (pending approver on the seeded requests; submitter of the invoice request)"], + "fixtures": { + "app": "showcase", + "requires": [ + "the three seeded demo requests (invoice unanimous / EXP-DEMO quorum / EXP-2001 per-group) plus fresh showcase_budget_approval requests raised on demand by PATCHing a showcase_project budget above 100000 (budget != previous.budget)", + "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" + ] + }, + "variants": ["approve", "reject", "reassign", "revise (send-back)", "resubmit", "recall", "remind", "request-info", "comment"], + "steps": [ + "boot showcase isolated (dogfood §0); sign in as the dev admin", + "approve: POST /api/v1/approvals/requests/:id/approve {comment} on the EXP-DEMO request; re-read status + run", + "reject: raise a fresh budget-approval request (PATCH a project budget to e.g. 200000); POST /:id/reject {comment}; re-read — the flow resumes down its reject edge", + "revise: raise another budget-approval request; POST /:id/revise {comment} as the pending manager — re-read status; GET the flow run (parked at the approval_revise node 'wait_revision')", + "resubmit: POST /:id/resubmit {comment} as the submitter — re-read: pending again, round 2, fresh slate; then drive revise→resubmit→revise again and attempt a THIRD revise (maxRevisions: 2 — it must auto-reject)", + "recall: raise one more pending request; POST /:id/recall as the submitter; re-read", + "reassign: on a pending request POST /:id/reassign {to: , comment}; re-read pending_approvers", + "remind: POST /:id/remind on the admin-submitted invoice request; then POST it again immediately (throttle probe)", + "request-info + comment: POST /:id/request-info {comment} and /:id/comment {comment} on a pending request", + "after EVERY action above: GET /:id and GET /:id/actions; record the status transition and the appended timeline row", + "lock contrast: while a request from exec_review (lockRecord: true) is pending, PATCH the project record — refused; while parked at manager_review (lockRecord: false) the same PATCH succeeds", + "exec no-revise gate: drive a >500000 budget to exec_review and POST /:id/revise — expect a clear refusal" + ], + "acceptance": [ + { + "clause": "per-variant: each of the nine actions executes its POST /api/v1/approvals/requests/:id/ route and appends exactly one timeline action row naming the actor and the action kind — every variant individually verified", + "oracle": "api", + "verify": "per-variant table: route called, response status, the new /:id/actions row; a variant with no row (or two) fails that variant", + "evidence": "per-variant request/response + actions reads" + }, + { + "clause": "decisions finalize per the APPROVAL_STATUSES lifecycle: approve → approved, reject → rejected, and the parked flow run resumes down the matching branch label", + "oracle": "api", + "verify": "request status transitions pending→approved / pending→rejected; the owning run transitions paused→completed with the approve/reject edge taken", + "evidence": "before/after request + run reads" + }, + { + "clause": "revise (send-back) moves pending→returned and parks the run at the service-owned approval_revise node; resubmit moves returned→pending as round 2 with a fresh approver slate", + "oracle": "api", + "verify": "after revise: status=returned, run paused at 'wait_revision'; after resubmit: status=pending, slate repopulated; the resubmit is refused for anyone but the submitter", + "evidence": "reads after each move" + }, + { + "clause": "the maxRevisions guard holds: the third send-back auto-rejects instead of looping forever", + "oracle": "api", + "verify": "after two revise/resubmit rounds, the next revise finalizes the request rejected (maxRevisions: 2 on manager_review)", + "evidence": "the third-revise response + final read" + }, + { + "clause": "recall is submitter-only and moves pending→recalled; the approver's task disappears from the inbox", + "oracle": "api", + "verify": "recall as submitter succeeds (status=recalled); recall attempted by a non-submitter is FORBIDDEN; 待我审批 no longer lists it", + "evidence": "reads + the denied call" + }, + { + "clause": "non-finalizing actions mutate only what they own: reassign swaps the slate slot to the target user; remind/request-info/comment leave status=pending and touch nothing but the timeline", + "oracle": "api", + "verify": "after reassign: pending_approvers contains the target and not the source; after remind/request-info/comment: status and tallies byte-identical, one new timeline row each", + "evidence": "before/after reads per action" + }, + { + "clause": "lockRecord is enforced on both sides: the exec step (lockRecord: true) refuses record edits while pending; the manager step (lockRecord: false) permits them", + "oracle": "api", + "verify": "PATCH the project while each step is pending: refused under exec_review, accepted under manager_review (the objectui#2902 pair)", + "evidence": "the two PATCH responses" + } + ], + "negative": [ + "send-back at exec_review reporting success is a FAIL — the step declares no revise edge and the service must refuse with a clear error", + "an immediately repeated remind must be throttled ('a reminder was sent recently') — a silent second success is a FAIL", + "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"], + "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)", + "packages/rest/src/rest-server.ts (flowMoveRoute: revise=pending approver, resubmit=submitter; threadRoute access per action; recall submitter-only)", + "packages/spec/src/contracts/approval-service.ts (APPROVAL_STATUSES: pending|approved|rejected|recalled|returned)", + "examples/app-showcase/src/automation/flows/index.ts (BudgetApprovalFlow — ADR-0044 revise loop, maxRevisions 2, exec step without a revise edge, lockRecord pair)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — decision-action matrix derived from the approvals REST route ledger and the ADR-0044 revise/resubmit flow shape", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "approvals.decision-only-via-service", + "title": "An approval-parked run cannot be resumed through the generic automation route — decisions go through the approvals service only", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": ["dev admin (holds finance + legal, so the seeded unanimous request is decidable)"], + "fixtures": { + "app": "showcase", + "requires": ["showcase_invoice_signoff parked at its aggregating approval node (the seeded invoice request, or a fresh one via PATCHing an invoice draft→sent)"] + }, + "steps": [ + "boot showcase isolated (dogfood §0); sign in as the dev admin", + "locate the paused run: GET /api/v1/automation/showcase_invoice_signoff/runs — status=paused at the dual_signoff approval node; note the runId and the matching approval request id", + "forge the generic resume: POST /api/v1/automation/showcase_invoice_signoff/runs/:runId/resume with {} and again with {branchLabel: 'approve'}", + "re-read the run and the approval request after each forged attempt", + "decide properly: POST /api/v1/approvals/requests/:id/approve until the unanimous slate is satisfied; re-read request and run", + "contrast (the other side of the gate): pause showcase_reassign_wizard at its screen node via the Tasks row action, then resume THAT run through the same generic route with valid inputs" + ], + "acceptance": [ + { + "clause": "the generic resume route answers 403 for a run parked on an approval node (resumeAuthority: 'service', #3801) — with or without a branchLabel in the body", + "oracle": "api", + "verify": "both forged POSTs answer 403; neither 2xx nor a 404 that would mask the gate (the run exists)", + "evidence": "the two rejected responses" + }, + { + "clause": "the forged attempts change nothing: the run stays paused and the request records no decision", + "oracle": "api", + "verify": "run re-read: status=paused at dual_signoff; GET /:id/actions: no new action row after the forged calls", + "evidence": "before/after run + actions reads" + }, + { + "clause": "the ApprovalService decision is the door that works: satisfying the slate resumes the run down the approve edge to completion", + "oracle": "api", + "verify": "after the approvals-API decisions: request status=approved; the run transitions paused→completed with the notify_cleared step executed", + "evidence": "final request + run reads" + }, + { + "clause": "the gate is node-scoped, not route-dead: a run paused at a SCREEN node resumes fine through the same generic route", + "oracle": "api", + "verify": "the showcase_reassign_wizard resume with valid inputs answers 2xx and completes its run — proving the 403 above is the approval-node gate, not a broken route", + "evidence": "the accepted screen resume + its run read" + } + ], + "negative": [ + "a generic resume that answers 2xx on an approval pause is a FAIL of the #3801 gate — fail-open by omission is the exact regression this pins, and a passing screen-flow resume is REQUIRED alongside it to prove the test hit the gate rather than a dead route" + ], + "traps": ["dispatcher-vs-hono-route"], + "automated": { "kind": "test", "ref": "packages/services/service-automation/src/resume-authority-gate.test.ts" }, + "source": [ + "packages/runtime/src/route-ledger.ts (the resume route's #3801 note: resumeAuthority 'service' → 403, decisions via ApprovalService.decide)", + "examples/app-showcase/src/automation/flows/index.ts (InvoiceDualSignoffFlow — documents the exact 403 + approvals-API sequence)", + "packages/spec/src/automation/approval.zod.ts (APPROVAL_NODE_TYPE; ADR-0039 Track A aggregating node)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — pins the #3801 resume-authority gate as a both-sides item (approval 403 + screen 2xx contrast)", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "approvals.dynamic-approver-routing", + "title": "A decision's typed outputs route the next stage: expression approvers resolve from the previous decision at node entry", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": ["dev admin (org-membership owner — the stage-1 approver of the dynamic-approval demo)"], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_dynamic_approval (#3447 P2): stage 1 routes to org_membership_level 'owner' with a REQUIRED decisionOutput next_reviewers (typed user multi picker); stage 2 resolves an expression approver over vars from that output, with onEmptyApprovers: 'fail'", + "trigger: retitle a showcase_announcement (an otherwise approval-free object, so this demo never collides with the expense/invoice/project approval dedupe)" + ] + }, + "steps": [ + "boot showcase isolated (dogfood §0); sign in as the dev admin", + "PATCH a showcase_announcement's title over /api/v1/data — showcase_dynamic_approval opens its stage-1 Lead Review request routed at the org owner", + "GET /api/v1/approvals/requests/:id — record status and the declared decisionOutputs contract on the request/node", + "open the request drawer; screenshot the decision dialog's next_reviewers control — it must be a sys_user MULTI-SELECT picker, not a free-text box", + "gate probe: attempt to approve WITHOUT filling next_reviewers; record the refusal", + "approve WITH next_reviewers=[the admin] via the dialog; capture the decision POST body", + "GET the requests list again — the stage-2 co-sign request must now exist; read its pending_approvers", + "GET /api/v1/automation/showcase_dynamic_approval/runs/:runId — the run's variables carry the stage-1 outputs; decide stage 2 and confirm the run completes" + ], + "acceptance": [ + { + "clause": "the stage-1 decision dialog renders the TYPED control the metadata declares: a required sys_user multi-select for next_reviewers", + "oracle": "screenshot", + "verify": "dialog screenshot shows a record picker (multi), not free text — the #3508 degraded-to-text failure is the counter-case", + "evidence": "decision-dialog screenshot" + }, + { + "clause": "approve without the required output is REFUSED and the request stays pending", + "oracle": "api", + "verify": "the outputless approve is rejected (required decision output enforced on approve, objectui#2955); request re-reads status=pending with no new approval action row", + "evidence": "rejected attempt + re-read" + }, + { + "clause": "the accepted decision stores its outputs and stage 2's expression approver resolves EXACTLY the picked users at node entry", + "oracle": "api", + "verify": "the stage-2 request's pending_approvers equal the user ids submitted in next_reviewers — no more, no fewer", + "evidence": "decision POST body + stage-2 request read" + }, + { + "clause": "the decision outputs ride the flow run's variables (the vars.* the stage-2 expression reads), observable on the run detail", + "oracle": "api", + "verify": "run read between the stages: the stage-1 outputs present in the run variables snapshot", + "evidence": "run-detail read" + }, + { + "clause": "deciding stage 2 completes the run end to end", + "oracle": "api", + "verify": "after the co-sign decision: request finalized and the run transitions paused→completed", + "evidence": "final request + run reads" + } + ], + "negative": [ + "an outputless approve that succeeds is a FAIL — stage 2 declares onEmptyApprovers: 'fail', so a skipped required output would kill the run downstream; the enforcement must be at the decision, not the crash site" + ], + "traps": ["automation-input", "stale-console-bundle"], + "source": [ + "examples/app-showcase/src/automation/flows/dynamic-approval.flow.ts (#3447 P2)", + "packages/spec/src/automation/approval.zod.ts (DecisionOutputDefSchema — typed pickers, required-to-approve objectui#2955; expression approvers + resolveAs)", + "examples/app-showcase/src/automation/flows/approver-bindings.flow.ts (#3508 — the degraded-to-free-text failure the typed control fixes)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — covers the #3447 dynamic-routing chain (typed decision outputs → vars → expression approvers) with the required-output gate", "ref": "claude/platform-test-checklist-ocwugl" } + ] } ] } diff --git a/docs/qa/platform-checklist/areas/platform-core.json b/docs/qa/platform-checklist/areas/platform-core.json index b998f86bb9..3c792df7cb 100644 --- a/docs/qa/platform-checklist/areas/platform-core.json +++ b/docs/qa/platform-checklist/areas/platform-core.json @@ -1,107 +1,423 @@ { "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md.", "area": "platform-core", - "title": "Platform core — boot, health, console shell", + "title": "Platform core — boot, health, console shell, metadata pipeline", "items": [ { "id": "platform-core.boot-health", - "title": "Showcase boots clean: health 200, no degraded startup banners", + "title": "Showcase boots clean: health + ready 200, no degraded startup banners, console + app metadata served", "since": "v15", "status": "active", - "revision": 1, + "revision": 2, "priority": "P0", "surface": "mixed", "preconditions": [ - "isolated run per dogfood-verification §0: own free port, own file DB (--seed-admin -d file:/tmp//data.db)" + "isolated run per dogfood-verification §0: own free port (not 3000/3001/3210, checked with lsof), own file DB (--seed-admin -d file:/tmp//data.db)" ], "steps": [ - "boot examples/app-showcase via `objectstack dev --ui --seed-admin` on an isolated port", - "poll GET /api/v1/health until 200", - "read the full boot log" + "boot examples/app-showcase via `objectstack dev --ui --seed-admin -p -d file:/tmp//data.db`", + "poll GET http://localhost:/api/v1/health until 200; record time-to-healthy", + "GET /api/v1/ready and record the status (the readiness probe is a separate route — packages/runtime/src/route-ledger.ts)", + "read the FULL boot log: capture the Flows: banner, any ⚠ lines, any ERROR-level lines, and every SeedLoader line", + "GET /_console/ and confirm the console shell HTML is served (200, text/html)", + "GET /api/v1/meta/app?id=com.example.showcase and capture the merged app/nav metadata", + "cross-check the nav payload: the grp_data group lists the seeded objects (showcase_project, showcase_task, showcase_account, showcase_contact, showcase_invoice, showcase_field_zoo, …)" ], "acceptance": [ { "clause": "GET /api/v1/health returns 200 within the boot window", "oracle": "api", "verify": "curl -s -o /dev/null -w '%{http_code}' http://localhost:/api/v1/health → 200", + "evidence": "the curl output + time-to-healthy" + }, + { + "clause": "GET /api/v1/ready returns 200 once boot completes (readiness, not just liveness)", + "oracle": "api", + "verify": "curl the /ready probe after health goes green", "evidence": "the curl output" }, { - "clause": "the `Flows:` startup banner reports no ⚠ misauthored flows and no ERROR lines appear during boot", + "clause": "the `Flows:` startup banner reports no ⚠ misauthored flows and no ERROR-level lines appear anywhere in the boot log", "oracle": "log", - "verify": "grep the boot log for '⚠' in the Flows banner and for ERROR-level lines; seed rejections count as failures (see #3415 — SeedLoader rejections were silent)", + "verify": "grep the boot log for '⚠' in the Flows banner and for ERROR lines; seed rejections count as failures (see #3415 — SeedLoader rejections were silent)", "evidence": "the grepped log excerpt" }, { - "clause": "the console shell is served at /_console/ and the seeded app resolves", + "clause": "the console shell is served at /_console/ and the seeded app's merged metadata resolves", "oracle": "api", - "verify": "GET /api/v1/meta/app?id= returns the merged app/nav metadata", - "evidence": "response status + top-level keys" + "verify": "GET /_console/ → 200 text/html; GET /api/v1/meta/app?id=com.example.showcase returns the merged app/nav metadata (dogfood skill §1 names this exact endpoint)", + "evidence": "response statuses + top-level keys" + }, + { + "clause": "the served nav matches the authored app: every grp_data object entry from src/ui/apps/index.ts appears in the meta/app response — nothing silently dropped at merge", + "oracle": "api", + "verify": "diff the nav object names in the response against examples/app-showcase/src/ui/apps/index.ts", + "evidence": "the diff (empty)" } ], - "traps": ["seed-data-thin"], - "source": ["dogfood-verification skill §0–§1", "#3415"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — standing P0 smoke distilled from the dogfood boot protocol", "ref": "#3358" }] + "negative": [ + "any SeedLoader rejection line in the boot log is a FAIL against the seed (RUNNER rule 3: a defect in the fixture is a fail, not a block — #3408/#3415)", + "a health 200 with ERROR lines in the log is NOT a clean boot — the log clause fails independently of the probe" + ], + "traps": ["seed-data-thin", "stale-dist"], + "source": [ + "dogfood-verification skill §0–§1", + "#3415", + "packages/runtime/src/route-ledger.ts (GET /health, GET /ready)", + "examples/app-showcase/src/ui/apps/index.ts (authored nav)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — standing P0 smoke distilled from the dogfood boot protocol", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { - "id": "platform-core.console-login", - "title": "Seeded admin can sign in through the console login page", + "id": "platform-core.seed-integrity", + "title": "Seed integrity: row counts match the authored seed, values land verbatim, replay is idempotent", "since": "v15", "status": "active", "revision": 1, "priority": "P0", + "surface": "api", + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { + "app": "showcase", + "requires": [ + "a FRESH isolated boot (new file DB) so first-boot seed behavior is what is measured, then a restart against the SAME DB for the idempotence clause" + ] + }, + "steps": [ + "after a clean fresh boot, derive the expected per-object row counts from the app's own seed module (examples/app-showcase/src/data/seed/index.ts — count the records arrays; re-derive rather than trusting a stale list if seeds changed)", + "authoring-time baseline for cross-checking the derivation: showcase_account=14, showcase_contact=33 (9 named + 24 bulk prospects), showcase_project=5, showcase_task=10, showcase_invoice=12, showcase_invoice_line=5, showcase_expense_report=4, showcase_expense_line=13, showcase_field_zoo=2", + "for each seeded object, GET /api/v1/data/?$top=1 and read the server total; build the expected-vs-actual table", + "GET the Field Zoo 'Specimen — Full' row and spot-diff authored values (f_multiselect ['red','green'] as a set, f_lookup resolving to the Northwind account id, f_json nested object intact)", + "re-read the boot log's seed lines: every seed reports success; no rejection or partial-load line", + "restart the server against the SAME file DB; re-run the count sweep and diff against the first sweep (upsert mode must no-op, not duplicate)" + ], + "acceptance": [ + { + "clause": "every seeded object's server row count equals the count authored in the seed module — no silent partial load", + "oracle": "api", + "verify": "expected-vs-actual table from GET /api/v1/data/?$top=1 totals vs the records arrays in src/data/seed/index.ts; zero mismatches", + "evidence": "the table" + }, + { + "clause": "seed VALUES land verbatim, not just rows: the Specimen — Full spot-diff matches the authored literal (arrays as sets, JSON objects structurally, references resolved to real ids)", + "oracle": "api", + "verify": "field-by-field diff of the API read against the seed literal for the sampled fields", + "evidence": "the diff" + }, + { + "clause": "the boot log reports every seed load as success — a rejection is a FAIL against the seed even when the server otherwise boots green (#3415: four of five projects were silently rejected once)", + "oracle": "log", + "verify": "grep the boot log for SeedLoader/seed lines; no rejected/failed entries", + "evidence": "the log excerpt" + }, + { + "clause": "seed replay is idempotent: a restart against the same DB changes no count (upsert with externalId no-ops on unchanged rows)", + "oracle": "api", + "verify": "second count sweep diffs empty against the first", + "evidence": "both sweeps" + }, + { + "clause": "deliberately-unseeded fields stay unseeded for their documented reasons (f_user/f_users: sys_user rows come from sign-up; f_secret: no CryptoProvider on the seed path; task.cover: a managed sys_file cannot be honestly seeded, #4891/ADR-0104) — their absence is CORRECT, not a gap to 'fix'", + "oracle": "api", + "verify": "the Specimen reads show these fields null/absent; record them as expected-absent in the run, never as findings", + "evidence": "the reads + the expected-absent list" + } + ], + "negative": [ + "a count of 0 on any nav-visible object is a FAIL against the seed, never 'feature has nothing to show' (seed-data-thin trap: check row counts vs the built artifact, read the boot log)", + "counts that GROW on restart are a FAIL of upsert idempotence even though every row individually looks valid" + ], + "traps": ["seed-data-thin", "single-datapoint"], + "source": [ + "examples/app-showcase/src/data/seed/index.ts (authored counts + expected-value comments)", + "#3408", + "#3415", + "packages/spec/src/data/seed-loader.zod.ts", + "RUNNER.md trap 'seed-data-thin'" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — makes the #3408/#3415 class of silent seed failure a standing P0 check with exact authored baselines", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "platform-core.console-login", + "title": "Seeded admin signs in through the console; the session survives reload and re-authenticates cleanly after expiry", + "since": "v15", + "status": "active", + "revision": 2, + "priority": "P0", "surface": "browser", "personas": ["seeded admin (admin@objectos.ai / admin123)"], "steps": [ - "open /_console/ in the browser", - "sign in with the seeded admin credentials", - "reload the page" + "open /_console/ in the browser; screenshot the login form", + "sign in with the seeded admin credentials — drive the React controlled inputs with the native setter + input/change events, or POST the auth endpoint from the page (dogfood skill §4; naive fills submit empty)", + "screenshot the post-login shell; capture the first authed API responses", + "reload the page; capture the first authed API request after reload", + "expire the session: clear the auth cookies for the origin; then trigger an authed navigation/API call", + "observe the console's reaction (redirect to login vs dead shell); screenshot", + "sign in again and verify the console restores a working session on the same route", + "negative pass: sign out, then attempt login with a wrong password; capture the auth response and the UI" ], "acceptance": [ { - "clause": "login succeeds and lands in the console shell", + "clause": "login succeeds and lands in the console shell (nav + header rendered, not the login form)", "oracle": "screenshot", - "verify": "post-login screenshot shows the app shell (nav + header), not the login form", + "verify": "post-login screenshot shows the app shell", "evidence": "screenshot" }, { - "clause": "the session survives a reload", + "clause": "the session survives a reload — the first authed API call after reload returns 200 with no redirect back to login", + "oracle": "network", + "verify": "network trace of the first authed request after reload (e.g. GET /api/v1/meta/app?id=com.example.showcase)", + "evidence": "the trace" + }, + { + "clause": "an expired/cleared session is answered 401 by the SERVER on authed API calls — the deny side of the auth gate, proven on the wire", "oracle": "network", - "verify": "after reload, authed API calls return 200 (no redirect back to login)", - "evidence": "network trace of the first authed request after reload" + "verify": "after clearing cookies, the authed API call in the trace returns 401 (auth is the better-auth passthrough at /api/v1/auth/** — packages/runtime/src/route-ledger.ts; plugin-auth/src/auth-route-ledger.ts)", + "evidence": "the 401 trace" + }, + { + "clause": "the console reacts to expiry by returning the user to login (or an explicit re-auth prompt) — never a dead shell rendering stale data as if authed", + "oracle": "screenshot", + "verify": "post-expiry screenshot shows the login/re-auth surface", + "evidence": "screenshot" + }, + { + "clause": "re-authentication after expiry restores a working session: the same authed calls return 200 again and the shell renders current data", + "oracle": "network", + "verify": "post-re-login trace shows 200s; a mutation or fresh read succeeds", + "evidence": "the trace + screenshot" } ], "negative": [ - "a wrong password is rejected with a visible error, not a silent no-op" + "a wrong password is rejected with a visible, named error AND no session cookie is set — a silent no-op or an error-free bounce is a FAIL", + "an expired session that keeps serving the shell with cached data (no 401, no redirect) is a FAIL of the expiry clause" + ], + "traps": ["automation-input", "shared-browser-tab"], + "source": [ + "dogfood-verification skill §4 (React controlled inputs; auth POST fallback)", + "packages/runtime/src/route-ledger.ts ('* /auth/**' better-auth passthrough)", + "--seed-admin credentials: dogfood-verification skill §0" ], - "traps": ["automation-input"], - "source": ["dogfood-verification skill §4 (React controlled inputs)"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial", "ref": "#3358" }] + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "platform-core.nav-surfaces-render", - "title": "Every showcase nav surface renders without page errors", + "title": "Every showcase nav surface renders without page errors, and failures surface the error boundary — never a blank page", "since": "v15", "status": "active", - "revision": 1, + "revision": 2, "priority": "P0", "surface": "browser", + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { + "app": "showcase", + "knownGaps": [ + "no stock showcase fixture deliberately throws inside a route, so the ErrorBoundary fallback ('Something went wrong' + Try Again / Go Home — objectui packages/app-shell/src/chrome/ErrorBoundary.tsx) is verified opportunistically on any failure encountered, plus via the bad-route probe below" + ] + }, "steps": [ - "walk every nav destination in the showcase app (pages, list views, dashboards, reports, view gallery)", - "on each: wait for render, then check for pageerror / empty main / placeholder leaks" + "run the pinned suite: pnpm -C examples/app-showcase test:smoke (the SURFACES array in e2e/showcase-smoke.spec.ts — 31 surfaces: pages, object lists, dashboards, reports, the view-gallery pages)", + "GET /api/v1/meta/app?id=com.example.showcase and extract every nav destination from the response", + "diff the served nav destinations against the SURFACES array; hand-walk any destination the suite does not cover (screenshot first, then DOM)", + "on each hand-walked surface: wait for render, screenshot, then check for pageerror / empty
/ placeholder leaks ('no actions configured')", + "probe the failure path: navigate to a nonexistent route under the app (/_console/apps/com.example.showcase/object_that_does_not_exist); screenshot what renders", + "if ANY surface fails during the sweep, verify the failure presents as the shell's error boundary or a named empty/error state — capture it" ], "acceptance": [ { "clause": "no surface throws a pageerror, renders an empty
, or leaks a 'no actions configured' placeholder; chart surfaces draw a real SVG", "oracle": "test", - "verify": "run the pinned suite: pnpm -C examples/app-showcase test:smoke (SURFACES array in e2e/showcase-smoke.spec.ts)", + "verify": "pnpm -C examples/app-showcase test:smoke (SURFACES array in e2e/showcase-smoke.spec.ts) — green", "evidence": "test run output" + }, + { + "clause": "the smoke's coverage is CURRENT: every nav destination served in meta/app is either in the SURFACES array or hand-walked this run — no surface silently outside the net", + "oracle": "api", + "verify": "diff of served nav destinations vs SURFACES + the hand-walk records for the remainder", + "evidence": "the diff + per-surface screenshots" + }, + { + "clause": "a bad route renders a NAMED not-found/error state inside the shell — nav and header stay alive, never a white page or dead shell", + "oracle": "dom", + "verify": "after the screenshot confirms the shell rendered, assert the main region carries an explicit empty/error message for the nonexistent object", + "evidence": "screenshot + DOM excerpt" + }, + { + "clause": "any render failure encountered anywhere in the sweep surfaces the route-level ErrorBoundary fallback (recoverable via Try Again), not a blank page", + "oracle": "screenshot", + "verify": "if a failure occurs: screenshot shows the boundary fallback; Try Again re-mounts the route; record none-encountered explicitly otherwise", + "evidence": "failure screenshot or the explicit none-encountered note" } ], + "negative": [ + "a surface that renders a blank
is a FAIL even with no console error — transitional emptiness must be ruled out by the screenshot-first protocol, then the persistent blank is the finding", + "a bad route producing a white page (shell gone) is a FAIL of the error-boundary clause" + ], "automated": { "kind": "e2e", "ref": "examples/app-showcase/e2e/showcase-smoke.spec.ts" }, - "traps": ["hydration-race", "single-datapoint"], - "source": ["examples/app-showcase/e2e/showcase-smoke.spec.ts", ".github/workflows/showcase-smoke.yml"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — wraps the existing automated smoke as a checklist row so runs report it alongside manual items", "ref": "#3358" }] + "traps": ["hydration-race", "single-datapoint", "stale-console-bundle"], + "source": [ + "examples/app-showcase/e2e/showcase-smoke.spec.ts (SURFACES)", + ".github/workflows/showcase-smoke.yml", + "objectui: packages/app-shell/src/chrome/ErrorBoundary.tsx (route-level fallback + recovery actions)", + "examples/app-showcase/src/ui/apps/index.ts (nav truth)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — wraps the existing automated smoke as a checklist row so runs report it alongside manual items", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "platform-core.metadata-registry-serving", + "title": "The metadata registry is served over REST: /meta lists every registered type with its spec-derived create seed", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { "app": "showcase" }, + "steps": [ + "authenticated GET /api/v1/meta — the types listing (entries[]); capture the response", + "check the entries against DEFAULT_METADATA_TYPE_REGISTRY (packages/spec/src/kernel/metadata-plugin.zod.ts): the built-in kinds the showcase registers are present (object, view, page, dashboard, app, action, report, dataset, flow, seed, mapping, permission, position, translation, email_template, doc, book, datasource, api, …)", + "GET /api/v1/meta/types (the richer Studio listing — a distinct server-only route per packages/runtime/src/route-ledger.ts) and capture it", + "GET /api/v1/meta/view/showcase_task and confirm the stored view item is served with its authored shape (list + listViews + formViews keys)", + "GET /api/v1/meta/:type for 'view' and confirm the showcase's authored views are enumerated", + "run the pinned create-seed contract: pnpm --filter @objectstack/dogfood exec vitest run test/meta-types-create-seed.dogfood.test.ts" + ], + "acceptance": [ + { + "clause": "GET /api/v1/meta answers 200 with a non-empty entries[] naming the registered metadata types", + "oracle": "api", + "verify": "response parses; entries.length > 0; the registry kinds listed in the steps are all present", + "evidence": "the response + the presence table" + }, + { + "clause": "entries carry the AUTHORITATIVE spec-derived create seeds: dashboard's seed is {widgets: []}, action's is a script with a valid js body, and report exposes NO seed by design (canvas-create) — consumers derive create defaults from the spec, not re-invent them", + "oracle": "test", + "verify": "pnpm --filter @objectstack/dogfood exec vitest run test/meta-types-create-seed.dogfood.test.ts — green (it asserts entry.createSeed equals getMetadataCreateSeed(type) for every seeded registered type)", + "evidence": "test run output" + }, + { + "clause": "GET /api/v1/meta/types (the richer Studio-facing listing) is served — a real route distinct from GET /meta, per the route ledger", + "oracle": "api", + "verify": "authenticated GET returns 200 with the type registry payload", + "evidence": "the response" + }, + { + "clause": "a stored item is retrievable by type+name with its authored shape: GET /api/v1/meta/view/showcase_task returns the task view gallery as authored", + "oracle": "api", + "verify": "the response carries the authored keys (list, listViews incl. in_progress/board/gantt, formViews incl. wizard/quick)", + "evidence": "the response's key inventory" + }, + { + "clause": "type-scoped listing works: GET /api/v1/meta/view enumerates the showcase's authored views (task, project, contact, field-zoo, business-unit, inquiry families present)", + "oracle": "api", + "verify": "the listing contains the expected view names from examples/app-showcase/src/ui/views/", + "evidence": "the listing" + } + ], + "negative": [ + "a registered type missing from GET /meta (or served without its registry contract) is a FAIL — the Studio designer derives its create UX from this response, and drift here produced real create-save 422s (the dashboard-layout / action-body incidents the pinned test memorializes)", + "an unauthenticated GET /api/v1/meta that returns the full registry is a finding for the access-security area — capture and cross-file it, do not tick past it" + ], + "traps": ["dispatcher-vs-hono-route"], + "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/meta-types-create-seed.dogfood.test.ts" }, + "source": [ + "packages/spec/src/kernel/metadata-plugin.zod.ts (MetadataTypeSchema + DEFAULT_METADATA_TYPE_REGISTRY)", + "packages/spec/src/kernel/metadata-create-seeds.ts", + "packages/runtime/src/route-ledger.ts (GET /meta, GET /meta/types, GET /meta/:type, GET /meta/:type/:name)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — pins the registry-serving contract the Studio create path depends on", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "platform-core.metadata-authoring-roundtrip", + "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": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { + "app": "showcase", + "requires": [ + "a runtime-created WRITABLE package to author into (create one via Studio or POST /api/v1/packages) — the showcase's own code package is read-only by design (ADR-0070)" + ] + }, + "steps": [ + "create a writable package (Studio package switcher → new, or POST /api/v1/packages); record its id", + "save a draft view into it: PUT /api/v1/meta/view/os_qa_view_ with a minimal valid ListView body bound to showcase_task and the writable packageId; capture the response", + "GET /api/v1/meta/_drafts and confirm the draft is listed", + "publish: POST /api/v1/packages//publish-drafts; then GET /api/v1/meta/view/os_qa_view_/published and capture it", + "verify in the console that the published view is live for showcase_task (reload, open the view switcher)", + "denied side A: attempt the same runtime create targeting the read-only CODE package; capture the refusal (expect writable_package_required — ADR-0070 D1)", + "denied side B: attempt a PUT overlay against a PACKAGED object item (type 'object' has allowOrgOverride: false); capture the refusal (expect HTTP 403 not_overridable)", + "Studio browser half: create a new record page via Studio bound to an object and capture the PUT /api/v1/meta/page/... it issues" + ], + "acceptance": [ + { + "clause": "a draft saved via PUT /api/v1/meta/view/:name persists and is listed by GET /api/v1/meta/_drafts", + "oracle": "api", + "verify": "the PUT succeeds; the drafts listing contains os_qa_view_ (routes per packages/runtime/src/route-ledger.ts: PUT /meta/:type/:name, GET /meta/_drafts)", + "evidence": "PUT response + drafts listing" + }, + { + "clause": "publish-drafts promotes the draft: GET /meta/view/:name/published serves the authored body afterwards, and the console renders the view after reload", + "oracle": "api", + "verify": "POST /packages/:id/publish-drafts → success; the /published read returns the body; console screenshot shows the view in the switcher", + "evidence": "publish response + published read + screenshot" + }, + { + "clause": "Studio's designer authors through the same pipeline: creating a record page issues PUT /api/v1/meta/page/ bound to its object and seeded from the default layout", + "oracle": "network", + "verify": "capture the PUT during Studio create (pinned by objectui e2e/live/studio-record-page.spec.ts, which waits on exactly that request)", + "evidence": "the captured PUT" + }, + { + "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)", + "evidence": "the refusal response" + }, + { + "clause": "DENY side of the overlay gate: a per-org overlay write against a type with allowOrgOverride unset (object/field) answers HTTP 403 not_overridable, while view/dashboard (the ADR-0005 Phase 1 opt-ins) accept", + "oracle": "api", + "verify": "the object-targeting PUT returns 403 not_overridable (contract stated on allowOrgOverride in packages/spec/src/kernel/metadata-plugin.zod.ts); the view PUT from the happy path succeeded", + "evidence": "both responses side by side" + }, + { + "clause": "a malformed body is refused by schema validation, never stored: a PUT with an invalid shape for the type answers a named validation error (validateOnWrite; the #5206 lesson — an unvalidated store is the defect)", + "oracle": "api", + "verify": "PUT /api/v1/meta/view/os_qa_bad_ with a nonsense body (e.g. columns: 42) → 4xx naming the validation failure; a subsequent GET finds no stored item", + "evidence": "the refusal + the empty GET" + } + ], + "negative": [ + "a 200 on either deny-side attempt (read-only package, locked overlay type) is a FAIL — both gates are server-side contracts, not Studio courtesies", + "a malformed metadata body stored as-is (200 on PUT, garbage on GET) is a FAIL — this is the exact pre-#5271 'api' kind hole", + "a published view that never appears in the console after reload is a FAIL of the round-trip even though every API call returned success (check against a fresh objectui build before filing — stale-console-bundle)" + ], + "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, e2e/live/studio-object-designer.spec.ts" }, + "source": [ + "packages/runtime/src/route-ledger.ts (PUT /meta/:type/:name, GET /meta/_drafts, GET /meta/:type/:name/published, POST /packages/:id/publish-drafts)", + "packages/spec/src/kernel/metadata-plugin.zod.ts (allowOrgOverride 403 not_overridable contract; validateOnWrite; registry flags per type)", + "ADR-0070 via packages/qa/dogfood/test/package-first-authoring.dogfood.test.ts (writable_package_required)", + "ADR-0033 (drafts / publish)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — grounds the Studio authoring pipeline end-to-end with both deny gates as first-class clauses", "ref": "claude/platform-test-checklist-ocwugl" } + ] } ] } From 512b4be106c7e254be03dd4221b6113866d1d6d2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 8 Aug 2026 03:36:01 +0000 Subject: [PATCH 06/14] =?UTF-8?q?feat(qa):=20=E5=B9=B3=E5=8F=B0=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=B8=85=E5=8D=95=E7=BC=BA=E5=8F=A3=E5=85=A8=E6=8E=92?= =?UTF-8?q?=E6=9F=A5=20=E2=80=94=20=E8=A1=A5=E9=BD=90=E8=87=B3=20170=20?= =?UTF-8?q?=E9=A1=B9,=E4=BF=AE=E6=AD=A3=203=20=E5=A4=84=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E8=B1=81=E5=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five-angle capability-coverage sweep (console UI surfaces · spec enums · routes/settings · built-in apps page-by-page · docs claims) closed a class of systematic omissions and grew the checklist 84 → 170 items across 15 areas, authored by 8 area-partitioned writer agents grounded in real source. Headline gaps closed: - The three built-in console apps (Setup/Studio/Account) had ZERO nav coverage — nav-surfaces-render only swept the showcase app. Added builtin-apps-nav-render (P0) + settings-hub-roundtrip, session-list-revoke, api-key-ui-lifecycle, org/team management, oauth/consent, audit-log-browser, notification-center, package-lifecycle, app-management, and more. - Three coverage.json waivers were FACTUALLY STALE and are corrected to real items: api (showcase authors 2 live declarative endpoints), datasource (read-only SQLite federation fixture), mapping (showcase_inquiry_feed). Ratchet now 25 kinds mapped / 3 waived (book/doc display-only, hook indirect). - sharing-rules-widen (the marquee gap): criteria rules → materialized sys_record_share, explain attribution, §A13 seed-not-clobber. - Enumerable-surface matrices previously missing: validation rule types (6), action locations (7, ACTION_LOCATIONS), gantt/kanban/calendar drag persistence, bulk select-all-matching, adhoc filter/sort builder, grid personalization, lookup create-new, concurrent-edit OCC-409, discussion @mentions, and route-ledger-live-parity extended to all ledgers (#3361 class). Every item stays honest: 8 blocked items name the missing fixture rather than faking coverage; product defects found while grounding (saved-report schedule cross-owner delete, AppManagement client-only stubs, undo without ifMatch) are encoded as expected-fail probes and catalogued in FOLLOW-UPS.md for maintainer decision, alongside docs-drift findings (retired capabilities still advertised). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01YD9f6FYyMraUWYeJf53V43 --- docs/qa/platform-checklist/FOLLOW-UPS.md | 66 + docs/qa/platform-checklist/README.md | 26 + .../areas/access-security.json | 1299 +++++++++++- docs/qa/platform-checklist/areas/ai.json | 545 ++++- .../platform-checklist/areas/api-backend.json | 650 +++++- .../platform-checklist/areas/approvals.json | 232 +++ .../areas/attachments-storage.json | 581 ++++++ .../platform-checklist/areas/automation.json | 162 +- docs/qa/platform-checklist/areas/cli.json | 511 +++++ .../platform-checklist/areas/dashboards.json | 500 ++++- docs/qa/platform-checklist/areas/i18n.json | 54 +- .../areas/identity-auth.json | 836 +++++++- .../areas/integration-system.json | 1145 +++++++++-- .../areas/platform-core.json | 605 +++++- .../areas/records-forms.json | 1778 ++++++++++++++++- docs/qa/platform-checklist/areas/search.json | 471 +++++ .../areas/studio-authoring.json | 747 +++++++ docs/qa/platform-checklist/coverage.json | 183 ++ scripts/check-platform-checklist.mjs | 67 +- 19 files changed, 9977 insertions(+), 481 deletions(-) create mode 100644 docs/qa/platform-checklist/FOLLOW-UPS.md create mode 100644 docs/qa/platform-checklist/areas/attachments-storage.json create mode 100644 docs/qa/platform-checklist/areas/cli.json create mode 100644 docs/qa/platform-checklist/areas/search.json create mode 100644 docs/qa/platform-checklist/areas/studio-authoring.json create mode 100644 docs/qa/platform-checklist/coverage.json diff --git a/docs/qa/platform-checklist/FOLLOW-UPS.md b/docs/qa/platform-checklist/FOLLOW-UPS.md new file mode 100644 index 0000000000..91cd6e51d8 --- /dev/null +++ b/docs/qa/platform-checklist/FOLLOW-UPS.md @@ -0,0 +1,66 @@ +# Follow-ups — open items from the capability-coverage sweep (2026-08-08) + +Working document, NOT committed pending user confirmation. The gap items found by the +five-angle sweep have all been authored into `areas/*.json` (checklist grew 84 → 170 +items; the `api`/`datasource`/`mapping` coverage waivers were corrected). What remains +here is what the sweep surfaced that is **not** a checklist item: product defects to +decide on, and docs that promise retired capabilities. + +## 1. Product defects found during the sweep (decide handling) + +These are real runtime/UI defects the gap hunters hit while grounding items. Each is +captured inside the relevant checklist item as an **expected-fail probe** (so a run +records the actual behavior instead of ticking green), but they are defects, not test +gaps. Security-sensitive ones were deliberately NOT filed publicly — your call. + +| # | defect | evidence | captured in | sensitivity | +|---|---|---|---|---| +| D1 | **Saved-report schedule routes lack an owner check** — `report-service.ts` `unscheduleReport`/`listSchedules` ignore `_context`, so user B can delete user A's report schedule (cross-owner destructive access). The read/run/delete routes ARE owner-gated (deny-as-404); only the schedule routes leak. | packages/plugins/plugin-reports/src/report-service.ts (unscheduleReport/listSchedules) | dashboards.saved-report-ownership (known-gap probe clause) | **SECURITY — not filed publicly; awaiting your decision** | +| D2 | **AppManagementPage enable/disable/set-default/delete are client-only stubs** — the handlers call `toast.success()` with a `TODO: Replace with real API call` and issue no request; an admin sees "success" while nothing changes. | objectui apps/console/src/pages/system/AppManagementPage.tsx | platform-core.app-management-toggle (expected-fail probe) | UX-integrity — safe to file | +| D3 | **`useGlobalUndo.executeOp` issues a bare `ds.update` with no `ifMatch`** — record undo can silently clobber a concurrent edit (no OCC guard on the undo path). | objectui react/src/hooks/useGlobalUndo.ts | records-forms.record-edit-undo (observe-and-flag clause) | correctness — safe to file | +| D4 | **`SharedViewLink` builds dead `/share//?token=` URLs** — client-generated token, no matching console route (only `/s/:token`), no server persistence. Registered but unused. | objectui plugin-view/src/SharedViewLink.tsx | — (not an item; demo-grade) | low — file a cleanup issue | +| D5 | **List "Share" button is a no-op** — renders when `schema.sharing` is set but has no onClick. | objectui plugin-list/src/ListView.tsx | — | low — file a cleanup issue | +| D6 | **`/api/v1/datasources` admin CRUD has no route ledger** — mounted by serve.ts, absent from rest-route-ledger.ts (tranche-3 discipline gap). | packages/services/service-datasource/src/admin-routes.ts | integration-system.datasource-admin-lifecycle (source note) | low — internal discipline | + +## 2. Docs promise capabilities the runtime doesn't deliver (PD#10, docs side — file docs issues) + +The capability docs advertise features that were retired or never shipped. Under Prime +Directive #10 ("never advertise a capability the runtime doesn't deliver") these are +docs bugs, not checklist items. + +- **Recycle bin / soft delete** — promised in `content/docs/capabilities/{data,integrations}.mdx`, + but `enable.trash` was RETIRED ("every delete has always been a hard delete; soft delete + parked at #3146", object.zod.ts retired-key guidance). → fix docs or ship the feature. +- **Recently-visited lists** — `enable.mru` retired/never implemented. The console DOES ship a + recents rail (UnifiedSidebar) — reconcile whether the doc claim maps to that surface or a dead one. +- **TV display pages / discussion threads** — promised (analytics.mdx, build-without-code.mdx). + Discussion = the real chatter surface (now covered by records-forms.record-discussion-mentions); + display pages have no spec surface found → confirm removal or file. +- **Five data-depth scopes** (permissions.mdx) — `own_and_reports`/`unit`/`unit_and_below` are + ENTERPRISE (hierarchy-security). The open checklist correctly drives own/org only. Optional both-sides + probe: authoring an intermediate depth in the open edition must degrade LOUDLY (ADR-0049), not + silently to `own` — could become a checklist item if you want it. + +## 3. Fixtures worth adding (would un-block currently-blocked items) + +The 8 `blocked` items are blocked on missing stock fixtures, not on the platform. Adding +these to the showcase would make them runnable: + +- a `publicSharing.enabled` object → unblocks `access-security.share-link-capability-tokens`. +- one configured OIDC/social IdP → unblocks `identity-auth.oauth-app-consent-loop`, + `linked-accounts-social`, and the existing `sso-enforced-first-paint`. +- a gantt view with `dependenciesField` + `lockField` + `parentField` → unblocks the + fixture-gated variants of `records-forms.gantt-interactions`. +- a not-auto-bound audience suggestion → unblocks the confirm/dismiss half of + `access-security.suggested-binding-loop`. +- the `IMPORT_CONSOLE_LIVE` import-harness backend → unblocks `records-forms.import-job-undo-cancel`. +- an approval-escalation clock-control/`runEscalations()` harness → unblocks `approvals.sla-escalation`. +- a second signed-up (non-admin) user in seeds, or a documented sign-up step in the runner → + removes the recurring "needs a 2nd user" knownGap on several persona-gated items. + +## 4. Notes + +- `PENDING-GAPS.md` (the full deduped gap register that drove the authoring) can be deleted + once you've reviewed §1–§2 above — it was scaffolding; this file is the durable residue. +- Nothing in this sweep has been committed. The checklist itself (`areas/*.json`, `coverage.json`, + `README.md`, `RUNNER.md`, `scripts/check-platform-checklist.mjs`) is ready for your review. diff --git a/docs/qa/platform-checklist/README.md b/docs/qa/platform-checklist/README.md index ad1ad660e2..2c2f754857 100644 --- a/docs/qa/platform-checklist/README.md +++ b/docs/qa/platform-checklist/README.md @@ -19,6 +19,7 @@ docs/qa/platform-checklist/ README.md ← this file: what an item is, how to append / change / retire RUNNER.md ← how an AI runs the checklist accurately (verdicts, oracles, evidence) areas/*.json ← the ledger, sharded by feature area (append here) + coverage.json ← capability-coverage ratchet: every governed metadata kind → items or waiver runs/ ← durable run records (one JSON per executed sweep) ``` @@ -51,6 +52,9 @@ next-sequential numbers do. "evidence": "what artifact the run must capture" } ], "negative": ["…"], // the other side of every gate (deny/absence cases) + "variants": ["…"], // enumerable-surface matrix (field types, chart types, flow + // nodes, operators…) — derived from the spec's own Zod enums, + // source cited; one clause requires per-variant verification "traps": ["hydration-race"], // known false-positive risks (vocabulary in RUNNER.md) "automated": { "kind": "e2e", "ref": "path/to/pinning.test.ts" }, // set when a permanent test pins it "blocked": { "by": "fixture", "ref": "#NNNN" }, // standing blocker, waive-with-a-reference @@ -91,6 +95,28 @@ Design notes: showcase-side fixture gaps #3358 uncovered (#3408, #3409, #3415) each cost a sweep to rediscover; recording the gap on the item is what stops that. +## Capability coverage — every capability the platform has gets tested + +`coverage.json` makes "凡是有的能力, 都要测试" mechanical instead of aspirational. The +universe of capabilities is **derived, not hand-kept**: every metadata kind with a +`packages/spec/liveness/.json` ledger (the ADR-0049 governed set) must be mapped +to at least one checklist item, or waived with a written reason. The validator enforces +both directions — an unmapped kind fails CI (the platform grew a capability the +checklist doesn't test), and a mapped kind whose liveness ledger disappeared fails too +(the entry outlived the capability). This is the `examples/app-showcase/src/coverage.ts` +demonstrated-or-waived ratchet, applied to testing instead of demonstration. + +Enumerable surfaces *inside* a capability (49 field types, 20 chart types, flow node +types, query operators, decision actions, …) are covered by `variants` matrices on the +items themselves, each derived from the spec's own Zod enums with the source cited — +when the spec grows a variant, the matrix item's next revision must grow with it (and +the showcase `coverage.test.ts` ratchet will already be failing if the variant isn't +demonstrable at all). + +A waiver is a debt marker, not an exemption: it names what fixture or surface is +missing, so paying it down is a matter of adding the fixture and flipping the entry to +`items`. + ## How a release sweep works A release no longer gets a hand-written checklist. The sweep for `vN` is a **filter diff --git a/docs/qa/platform-checklist/areas/access-security.json b/docs/qa/platform-checklist/areas/access-security.json index 871dec7aa9..e1c7d8dbd8 100644 --- a/docs/qa/platform-checklist/areas/access-security.json +++ b/docs/qa/platform-checklist/areas/access-security.json @@ -11,10 +11,16 @@ "revision": 2, "priority": "P0", "surface": "api", - "personas": ["admin (first sign-in dev admin, platform posture)", "two plain members (everyone baseline = showcase_member_default only)"], + "personas": [ + "admin (first sign-in dev admin, platform posture)", + "two plain members (everyone baseline = showcase_member_default only)" + ], "fixtures": { "app": "showcase", - "requires": ["showcase_private_note (OWD private, member_default grants create/read/edit)", "two fresh sign-ups, e.g. rls-a@verify.test / rls-b@verify.test"] + "requires": [ + "showcase_private_note (OWD private, member_default grants create/read/edit)", + "two fresh sign-ups, e.g. rls-a@verify.test / rls-b@verify.test" + ] }, "steps": [ "boot showcase isolated (own port + file DB per dogfood skill §0); sign in as admin; sign up members A and B (fresh emails) — both hold only the everyone baseline showcase_member_default", @@ -63,13 +69,35 @@ "evidence": "report detail lines + the spot-check trace" } ], - "negative": ["the foreign by-id write must not silently succeed: a 2xx on the forged PATCH, or a 4xx that still mutated the row (verified by the admin re-read), is a FAIL even though every list looked correctly filtered"], - "traps": ["wrong-persona"], - "automated": { "kind": "verify", "ref": "packages/verify/src/rls.ts (objectstack verify) + packages/qa/dogfood/test/showcase-private-owd.dogfood.test.ts" }, - "source": ["packages/verify/src/rls.ts", "ADR-0057 D10", "packages/qa/dogfood/test/showcase-private-owd.dogfood.test.ts", "authz-conformance.matrix.ts rows rls-read / rls-by-id-write"], + "negative": [ + "the foreign by-id write must not silently succeed: a 2xx on the forged PATCH, or a 4xx that still mutated the row (verified by the admin re-read), is a FAIL even though every list looked correctly filtered" + ], + "traps": [ + "wrong-persona" + ], + "automated": { + "kind": "verify", + "ref": "packages/verify/src/rls.ts (objectstack verify) + packages/qa/dogfood/test/showcase-private-owd.dogfood.test.ts" + }, + "source": [ + "packages/verify/src/rls.ts", + "ADR-0057 D10", + "packages/qa/dogfood/test/showcase-private-owd.dogfood.test.ts", + "authz-conformance.matrix.ts rows rls-read / rls-by-id-write" + ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — standing P0, delegating the systematic sweep to @objectstack/verify and keeping the persona spot-check manual", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — standing P0, delegating the systematic sweep to @objectstack/verify and keeping the persona spot-check manual", + "ref": "#3358" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -80,10 +108,17 @@ "revision": 2, "priority": "P0", "surface": "api", - "personas": ["admin", "two non-admin members (owner forge is only meaningful non-privileged; everyone baseline grants private-note create/edit and contact is reachable read-only, so use a member with showcase_contributor where create is needed)"], + "personas": [ + "admin", + "two non-admin members (owner forge is only meaningful non-privileged; everyone baseline grants private-note create/edit and contact is reachable read-only, so use a member with showcase_contributor where create is needed)" + ], "fixtures": { "app": "showcase", - "requires": ["showcase_contact.lead_score is the seeded readonly:true field (no defaultValue — stripped writes read back null)", "showcase_private_note carries the owner_id anchor", "showcase_invoice.tax_rate + line quantity/unit_price/product lock via readonlyWhen record/parent.status == 'paid'"] + "requires": [ + "showcase_contact.lead_score is the seeded readonly:true field (no defaultValue — stripped writes read back null)", + "showcase_private_note carries the owner_id anchor", + "showcase_invoice.tax_rate + line quantity/unit_price/product lock via readonlyWhen record/parent.status == 'paid'" + ] }, "steps": [ "boot showcase isolated; admin session + two members Alice and Bob (fresh sign-ups); resolve their sys_user ids via the data API or objectql", @@ -138,13 +173,38 @@ "evidence": "bulk response + row reads" } ], - "negative": ["run the forge/transfer clauses as ADMIN too and confirm they SUCCEED where legitimately privileged — the guard must gate on privilege, not break the admin path (wrong-persona trap, both sides)", "isSystem writes must still set readonly fields (A1's isSystem carve-out): verify via an objectql write with context {isSystem:true} that lead_score IS settable system-side"], - "automated": { "kind": "dogfood", "ref": "packages/qa/dogfood/test/owner-anchor-and-bulk-writes.dogfood.test.ts (+ showcase-static-readonly.dogfood.test.ts, showcase-readonly-when-parent.dogfood.test.ts)" }, - "traps": ["wrong-persona"], - "source": ["#3358 §9", "release-15.1 plan §A1–A4", "#3106", "#5126", "examples/app-showcase/src/data/objects/contact.object.ts (lead_score)", "examples/app-showcase/src/data/objects/invoice.object.ts (readonlyWhen)"], + "negative": [ + "run the forge/transfer clauses as ADMIN too and confirm they SUCCEED where legitimately privileged — the guard must gate on privilege, not break the admin path (wrong-persona trap, both sides)", + "isSystem writes must still set readonly fields (A1's isSystem carve-out): verify via an objectql write with context {isSystem:true} that lead_score IS settable system-side" + ], + "automated": { + "kind": "dogfood", + "ref": "packages/qa/dogfood/test/owner-anchor-and-bulk-writes.dogfood.test.ts (+ showcase-static-readonly.dogfood.test.ts, showcase-readonly-when-parent.dogfood.test.ts)" + }, + "traps": [ + "wrong-persona" + ], + "source": [ + "#3358 §9", + "release-15.1 plan §A1–A4", + "#3106", + "#5126", + "examples/app-showcase/src/data/objects/contact.object.ts (lead_score)", + "examples/app-showcase/src/data/objects/invoice.object.ts (readonlyWhen)" + ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — merged from #3358 §9 and the 15.1 A-group rows these dogfood tests pin", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — merged from #3358 §9 and the 15.1 A-group rows these dogfood tests pin", + "ref": "#3358" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -155,10 +215,17 @@ "revision": 2, "priority": "P1", "surface": "mixed", - "personas": ["admin (drives the panel and the explain API)", "auditor (viewAllRecords persona whose access is explained)", "plain member (the deny case)"], + "personas": [ + "admin (drives the panel and the explain API)", + "auditor (viewAllRecords persona whose access is explained)", + "plain member (the deny case)" + ], "fixtures": { "app": "showcase", - "requires": ["a showcase_private_note owned by a third user (the probe record)", "auditor member granted showcase_auditor (sys_user_permission_set row, as showcase-permission-zoo.dogfood.test.ts does)"] + "requires": [ + "a showcase_private_note owned by a third user (the probe record)", + "auditor member granted showcase_auditor (sys_user_permission_set row, as showcase-permission-zoo.dogfood.test.ts does)" + ] }, "steps": [ "boot showcase with the console; as admin create the probe: a member-owned showcase_private_note; grant a second member showcase_auditor", @@ -200,13 +267,35 @@ "evidence": "screenshot + JSON diff" } ], - "negative": ["explain for the plain member must NOT show visible=true merely because the ADMIN is the one asking — the explanation is about the target user, not the caller (wrong-persona); cross-check with the plain member's own denied GET"], - "traps": ["hydration-race", "wrong-persona"], - "automated": { "kind": "dogfood", "ref": "packages/qa/dogfood/test/showcase-permission-zoo.dogfood.test.ts (explain vama_bypass case)" }, - "source": ["#3358 §5", "packages/rest/src/rest-route-ledger.ts (security-explain family)", "packages/plugins/plugin-security/src/explain-engine.ts"], + "negative": [ + "explain for the plain member must NOT show visible=true merely because the ADMIN is the one asking — the explanation is about the target user, not the caller (wrong-persona); cross-check with the plain member's own denied GET" + ], + "traps": [ + "hydration-race", + "wrong-persona" + ], + "automated": { + "kind": "dogfood", + "ref": "packages/qa/dogfood/test/showcase-permission-zoo.dogfood.test.ts (explain vama_bypass case)" + }, + "source": [ + "#3358 §5", + "packages/rest/src/rest-route-ledger.ts (security-explain family)", + "packages/plugins/plugin-security/src/explain-engine.ts" + ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358, adding the impersonated-read cross-check", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from #3358, adding the impersonated-read cross-check", + "ref": "#3358" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -217,7 +306,9 @@ "revision": 2, "priority": "P2", "surface": "mixed", - "personas": ["admin"], + "personas": [ + "admin" + ], "steps": [ "boot showcase with the console; open Studio → object designer and the permission matrix on an object belonging to a read-only (installed/locked) package", "screenshot the surface; then read the DOM state of the edit affordances (checkboxes, Save)", @@ -251,12 +342,31 @@ "evidence": "the two reads" } ], - "negative": ["a UI that greys the controls while the direct PUT succeeds is a FAIL of this item even though the screenshot looks correct — record it as a server-guard gap, not a UI polish issue"], - "traps": ["stale-console-bundle", "hydration-race"], - "source": ["#3358 §5", "packages/spec/src/api/error-code-ledger.zod.ts (@objectstack/metadata-protocol codes)", "ADR-0010 §3.3 (_lock)"], + "negative": [ + "a UI that greys the controls while the direct PUT succeeds is a FAIL of this item even though the screenshot looks correct — record it as a server-guard gap, not a UI polish issue" + ], + "traps": [ + "stale-console-bundle", + "hydration-race" + ], + "source": [ + "#3358 §5", + "packages/spec/src/api/error-code-ledger.zod.ts (@objectstack/metadata-protocol codes)", + "ADR-0010 §3.3 (_lock)" + ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from #3358", + "ref": "#3358" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -267,11 +377,20 @@ "revision": 1, "priority": "P0", "surface": "api", - "personas": ["admin", "one fresh member per permission set under test (set granted via a sys_user_permission_set row, as showcase-permission-zoo.dogfood.test.ts does)", "a second member as the foreign-row owner"], + "personas": [ + "admin", + "one fresh member per permission set under test (set granted via a sys_user_permission_set row, as showcase-permission-zoo.dogfood.test.ts does)", + "a second member as the foreign-row owner" + ], "fixtures": { "app": "showcase", - "requires": ["examples/app-showcase/access-matrix.json — it IS the expectation table (31 permission-set × object rows)", "seeded permission sets showcase_contributor/manager/executive/auditor/ops/member_default/guest_portal/field_ops_delegate (security bootstrap)"], - "knownGaps": ["every authenticated member ALSO holds the everyone baseline showcase_member_default additively (ADR-0090 D5) — the effective expectation per cell is the UNION of the tested set's row and the baseline's row for that object; compute the union before judging a cell, or a baseline-granted read will look like a matrix violation"] + "requires": [ + "examples/app-showcase/access-matrix.json — it IS the expectation table (31 permission-set × object rows)", + "seeded permission sets showcase_contributor/manager/executive/auditor/ops/member_default/guest_portal/field_ops_delegate (security bootstrap)" + ], + "knownGaps": [ + "every authenticated member ALSO holds the everyone baseline showcase_member_default additively (ADR-0090 D5) — the effective expectation per cell is the UNION of the tested set's row and the baseline's row for that object; compute the union before judging a cell, or a baseline-granted read will look like a matrix violation" + ] }, "steps": [ "boot showcase isolated; admin session; for each permission set in access-matrix.json create a fresh member (unique email) and grant exactly that set by inserting the sys_user_permission_set row (system context), mirroring showcase-permission-zoo.dogfood.test.ts", @@ -326,13 +445,41 @@ "evidence": "the matrix artifact" } ], - "negative": ["run one denied cell per set as ADMIN and confirm it succeeds — proves the denial came from the persona's grants, not from a broken route (wrong-persona, both sides)", "any withheld cell answering 2xx is a FAIL even if the created/changed data looks harmless; silent success is the defect"], - "variants": ["showcase_contributor", "showcase_manager", "showcase_executive", "showcase_auditor", "showcase_ops", "showcase_member_default", "showcase_guest_portal", "showcase_field_ops_delegate"], - "automated": { "kind": "verify", "ref": "packages/verify/src/verify.ts (runCrudVerification) + packages/verify/src/rls.ts (runRlsProofs) — objectstack verify; persona-grained cells remain manual" }, - "traps": ["wrong-persona", "seed-data-thin"], - "source": ["examples/app-showcase/access-matrix.json (variant source — the expectation table)", "examples/app-showcase/src/security/permission-sets.ts", "ADR-0090 D1/D5", "packages/qa/dogfood/test/showcase-permission-zoo.dogfood.test.ts"], + "negative": [ + "run one denied cell per set as ADMIN and confirm it succeeds — proves the denial came from the persona's grants, not from a broken route (wrong-persona, both sides)", + "any withheld cell answering 2xx is a FAIL even if the created/changed data looks harmless; silent success is the defect" + ], + "variants": [ + "showcase_contributor", + "showcase_manager", + "showcase_executive", + "showcase_auditor", + "showcase_ops", + "showcase_member_default", + "showcase_guest_portal", + "showcase_field_ops_delegate" + ], + "automated": { + "kind": "verify", + "ref": "packages/verify/src/verify.ts (runCrudVerification) + packages/verify/src/rls.ts (runRlsProofs) — objectstack verify; persona-grained cells remain manual" + }, + "traps": [ + "wrong-persona", + "seed-data-thin" + ], + "source": [ + "examples/app-showcase/access-matrix.json (variant source — the expectation table)", + "examples/app-showcase/src/security/permission-sets.ts", + "ADR-0090 D1/D5", + "packages/qa/dogfood/test/showcase-permission-zoo.dogfood.test.ts" + ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "new — CRUD × permission matrix grounded in access-matrix.json, per the deep-test contract", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "new — CRUD × permission matrix grounded in access-matrix.json, per the deep-test contract", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -343,10 +490,19 @@ "revision": 1, "priority": "P0", "surface": "api", - "personas": ["admin", "members A and B (baseline only)", "contributor members for the invoice/line and project cases", "ops (modifyAllRecords contrast on announcements)", "auditor (viewAllRecords contrast on lines)"], + "personas": [ + "admin", + "members A and B (baseline only)", + "contributor members for the invoice/line and project cases", + "ops (modifyAllRecords contrast on announcements)", + "auditor (viewAllRecords contrast on lines)" + ], "fixtures": { "app": "showcase", - "requires": ["one seeded object per model, from the access-matrix sharingModel column: showcase_private_note (private), showcase_announcement (public_read), showcase_account + showcase_project (public_read_write), showcase_invoice_line under showcase_invoice (controlled_by_parent)", "contributor invoice RLS owner == current_user.email (permission-sets.ts invoice_own_rows) — invoices must be created with owner set to the creating contributor's email"] + "requires": [ + "one seeded object per model, from the access-matrix sharingModel column: showcase_private_note (private), showcase_announcement (public_read), showcase_account + showcase_project (public_read_write), showcase_invoice_line under showcase_invoice (controlled_by_parent)", + "contributor invoice RLS owner == current_user.email (permission-sets.ts invoice_own_rows) — invoices must be created with owner set to the creating contributor's email" + ] }, "steps": [ "boot showcase isolated; create members A and B (baseline), two contributor members C1/C2 (grant showcase_contributor), an ops member, an auditor member", @@ -402,13 +558,36 @@ "evidence": "run record" } ], - "negative": ["the public_read foreign PATCH must fail server-side even when the console hides the edit button — drive it as a forged direct request; a 2xx there is a FAIL (D10)", "run C2's line probes ALSO as C1 to prove the denial is derivation, not a broken line route (wrong-persona both sides)"], - "variants": ["private", "public_read", "public_read_write", "controlled_by_parent"], - "automated": { "kind": "dogfood", "ref": "packages/qa/dogfood/test/showcase-private-owd.dogfood.test.ts + showcase-public-read-owd.dogfood.test.ts + controlled-by-parent.dogfood.test.ts + showcase-invoice-cbp.dogfood.test.ts" }, - "traps": ["wrong-persona"], - "source": ["packages/spec/src/security/sharing.zod.ts (the four-model enum — variant source)", "examples/app-showcase/access-matrix.json (sharingModel column)", "ADR-0055", "authz-conformance.matrix.ts rows owd-private / owd-public-read / controlled-by-parent"], + "negative": [ + "the public_read foreign PATCH must fail server-side even when the console hides the edit button — drive it as a forged direct request; a 2xx there is a FAIL (D10)", + "run C2's line probes ALSO as C1 to prove the denial is derivation, not a broken line route (wrong-persona both sides)" + ], + "variants": [ + "private", + "public_read", + "public_read_write", + "controlled_by_parent" + ], + "automated": { + "kind": "dogfood", + "ref": "packages/qa/dogfood/test/showcase-private-owd.dogfood.test.ts + showcase-public-read-owd.dogfood.test.ts + controlled-by-parent.dogfood.test.ts + showcase-invoice-cbp.dogfood.test.ts" + }, + "traps": [ + "wrong-persona" + ], + "source": [ + "packages/spec/src/security/sharing.zod.ts (the four-model enum — variant source)", + "examples/app-showcase/access-matrix.json (sharingModel column)", + "ADR-0055", + "authz-conformance.matrix.ts rows owd-private / owd-public-read / controlled-by-parent" + ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "new — OWD matrix over the four spec sharing models, per the deep-test contract", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "new — OWD matrix over the four spec sharing models, per the deep-test contract", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -419,11 +598,19 @@ "revision": 1, "priority": "P1", "surface": "mixed", - "personas": ["contributor (the FLS-restricted persona: showcase_project.budget/spent/budget_remaining readable:true, editable:false)", "admin (the unrestricted contrast)"], + "personas": [ + "contributor (the FLS-restricted persona: showcase_project.budget/spent/budget_remaining readable:true, editable:false)", + "admin (the unrestricted contrast)" + ], "fixtures": { "app": "showcase", - "requires": ["showcase_contributor.fields FLS on showcase_project budget figures (permission-sets.ts — keys are . qualified; bare keys silently enforce nothing, the compile-time lint security-fls-unqualified-key guards that)", "a seeded showcase_project row with a non-null budget"], - "knownGaps": ["stock showcase authors NO readable:false FLS grant, so the read-MASKING half (field absent/nulled on GET, plugin-security/src/field-masker.ts) has no stock fixture; to run it, author a scratch permission set carrying readable:false on a showcase_project field and grant it to a fresh member — if the run cannot author one through a supported surface, record that half blocked(fixture) rather than ticking on the write half alone"] + "requires": [ + "showcase_contributor.fields FLS on showcase_project budget figures (permission-sets.ts — keys are . qualified; bare keys silently enforce nothing, the compile-time lint security-fls-unqualified-key guards that)", + "a seeded showcase_project row with a non-null budget" + ], + "knownGaps": [ + "stock showcase authors NO readable:false FLS grant, so the read-MASKING half (field absent/nulled on GET, plugin-security/src/field-masker.ts) has no stock fixture; to run it, author a scratch permission set carrying readable:false on a showcase_project field and grant it to a fresh member — if the run cannot author one through a supported surface, record that half blocked(fixture) rather than ticking on the write half alone" + ] }, "steps": [ "boot showcase with the console; contributor member (grant showcase_contributor); pick a seeded showcase_project and record its budget via a system-context read", @@ -466,12 +653,30 @@ "evidence": "both GET bodies + screenshots" } ], - "negative": ["a UI-only lock is a FAIL: if the form disables the control but the direct PATCH mutates budget, record a server-guard gap (the API clause is the oracle, the DOM clause is corroboration only)"], - "traps": ["wrong-persona", "stale-console-bundle", "hydration-race"], - "automated": { "kind": "dogfood", "ref": "packages/qa/dogfood/test/showcase-permission-zoo.dogfood.test.ts (FLS budget case — write half only)" }, - "source": ["examples/app-showcase/src/security/permission-sets.ts (contributor FLS)", "packages/plugins/plugin-security/src/field-masker.ts (read-mask enforcement site)", "ADR-0090 D10 (mask intersection)"], + "negative": [ + "a UI-only lock is a FAIL: if the form disables the control but the direct PATCH mutates budget, record a server-guard gap (the API clause is the oracle, the DOM clause is corroboration only)" + ], + "traps": [ + "wrong-persona", + "stale-console-bundle", + "hydration-race" + ], + "automated": { + "kind": "dogfood", + "ref": "packages/qa/dogfood/test/showcase-permission-zoo.dogfood.test.ts (FLS budget case — write half only)" + }, + "source": [ + "examples/app-showcase/src/security/permission-sets.ts (contributor FLS)", + "packages/plugins/plugin-security/src/field-masker.ts (read-mask enforcement site)", + "ADR-0090 D10 (mask intersection)" + ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "new — FLS item per the deep-test contract; read-mask half carries an explicit fixture gap instead of an ungrounded step", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "new — FLS item per the deep-test contract; read-mask half carries an explicit fixture gap instead of an ungrounded step", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -482,10 +687,18 @@ "revision": 1, "priority": "P1", "surface": "api", - "personas": ["plain member M (creates the foreign probe inquiries via member_default create:true)", "manager (readScope org / writeScope own on showcase_inquiry)", "executive (readScope org, no write, on showcase_inquiry + showcase_private_note)", "ops (readScope org / writeScope org on showcase_inquiry)"], + "personas": [ + "plain member M (creates the foreign probe inquiries via member_default create:true)", + "manager (readScope org / writeScope own on showcase_inquiry)", + "executive (readScope org, no write, on showcase_inquiry + showcase_private_note)", + "ops (readScope org / writeScope org on showcase_inquiry)" + ], "fixtures": { "app": "showcase", - "requires": ["showcase_inquiry is OWD private with create granted to the baseline — depth only matters where the baseline stops (permission-sets.ts docblock)", "personas staffed by granting the sets (sys_user_permission_set) or assigning the bound positions manager/exec/ops (bind-position-sets.ts wires position → set at boot)"] + "requires": [ + "showcase_inquiry is OWD private with create granted to the baseline — depth only matters where the baseline stops (permission-sets.ts docblock)", + "personas staffed by granting the sets (sys_user_permission_set) or assigning the bound positions manager/exec/ops (bind-position-sets.ts wires position → set at boot)" + ] }, "steps": [ "boot showcase isolated; create member M plus manager/executive/ops personas; as M: POST /api/v1/data/showcase_inquiry {\"subject\": \"M probe\", ...minimal valid payload}; as the manager: POST their OWN inquiry", @@ -534,13 +747,33 @@ "evidence": "the outcome table" } ], - "negative": ["a manager PATCH of M's inquiry that answers 2xx is a FAIL even if some UI would have hidden the row — writeScope own must be enforced on the forged direct request; and the same PATCH as ops must SUCCEED, or the finding is a broken route rather than a working guard (wrong-persona, both sides)"], - "variants": ["manager: read org / write own", "executive: read org / write none", "ops: read org / write org"], - "automated": { "kind": "dogfood", "ref": "packages/qa/dogfood/test/showcase-scope-depth.dogfood.test.ts (+ showcase-scope-depth-write, showcase-scope-depth-fallback)" }, - "traps": ["wrong-persona"], - "source": ["examples/app-showcase/src/security/permission-sets.ts (ADR-0057 D1 dials)", "examples/app-showcase/access-matrix.json (readScope/writeScope columns — variant source)", "ADR-0057 D1"], + "negative": [ + "a manager PATCH of M's inquiry that answers 2xx is a FAIL even if some UI would have hidden the row — writeScope own must be enforced on the forged direct request; and the same PATCH as ops must SUCCEED, or the finding is a broken route rather than a working guard (wrong-persona, both sides)" + ], + "variants": [ + "manager: read org / write own", + "executive: read org / write none", + "ops: read org / write org" + ], + "automated": { + "kind": "dogfood", + "ref": "packages/qa/dogfood/test/showcase-scope-depth.dogfood.test.ts (+ showcase-scope-depth-write, showcase-scope-depth-fallback)" + }, + "traps": [ + "wrong-persona" + ], + "source": [ + "examples/app-showcase/src/security/permission-sets.ts (ADR-0057 D1 dials)", + "examples/app-showcase/access-matrix.json (readScope/writeScope columns — variant source)", + "ADR-0057 D1" + ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "new — scope-depth read/write asymmetry matrix, per the deep-test contract", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "new — scope-depth read/write asymmetry matrix, per the deep-test contract", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -551,10 +784,15 @@ "revision": 1, "priority": "P0", "surface": "api", - "personas": ["anonymous (no Authorization header)", "an authenticated member (the unaffected contrast)"], + "personas": [ + "anonymous (no Authorization header)", + "an authenticated member (the unaffected contrast)" + ], "fixtures": { "app": "showcase", - "requires": ["stock secure default — boot with NO requireAuth override, exactly as the dogfood pin does; the platform default is what a fresh production deployment gets"] + "requires": [ + "stock secure default — boot with NO requireAuth override, exactly as the dogfood pin does; the platform default is what a fresh production deployment gets" + ] }, "steps": [ "boot showcase on the platform-default auth posture; do NOT sign in for the probe half", @@ -602,14 +840,931 @@ "evidence": "run record" } ], - "negative": ["the destructive automation case must be denied too: anonymous DELETE /api/v1/automation/showcase_reassign_wizard answers 401 and the flow remains registered afterwards (verify by an authed GET /api/v1/automation listing it) — a 200 {deleted:true} is the exact #5519 regression"], - "variants": ["data (/api/v1/data)", "metadata (/api/v1/meta)", "actions (/api/v1/actions — dispatcher-mounted)", "automation (/api/v1/automation — dispatcher-mounted)", "batch (/api/v1/batch)", "security-explain (/api/v1/security/explain)"], - "automated": { "kind": "dogfood", "ref": "packages/qa/dogfood/test/showcase-anonymous-deny-surfaces.dogfood.test.ts" }, - "traps": ["dispatcher-vs-hono-route"], - "source": ["release-15.1 plan §A8", "#2567", "#5519/#5569/#5570", "#5632", "authz-conformance.matrix.ts anonymous-deny rows (covers keys)", "packages/core/src/security/anonymous-deny.ts"], + "negative": [ + "the destructive automation case must be denied too: anonymous DELETE /api/v1/automation/showcase_reassign_wizard answers 401 and the flow remains registered afterwards (verify by an authed GET /api/v1/automation listing it) — a 200 {deleted:true} is the exact #5519 regression" + ], + "variants": [ + "data (/api/v1/data)", + "metadata (/api/v1/meta)", + "actions (/api/v1/actions — dispatcher-mounted)", + "automation (/api/v1/automation — dispatcher-mounted)", + "batch (/api/v1/batch)", + "security-explain (/api/v1/security/explain)" + ], + "automated": { + "kind": "dogfood", + "ref": "packages/qa/dogfood/test/showcase-anonymous-deny-surfaces.dogfood.test.ts" + }, + "traps": [ + "dispatcher-vs-hono-route" + ], + "source": [ + "release-15.1 plan §A8", + "#2567", + "#5519/#5569/#5570", + "#5632", + "authz-conformance.matrix.ts anonymous-deny rows (covers keys)", + "packages/core/src/security/anonymous-deny.ts" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-07", + "change": "new — auth-boundary sweep over every mounted family, grounded in the #2567/#5519 conformance rows and their dogfood pin", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "access-security.sharing-rules-widen", + "title": "Criteria sharing rules widen the OWD baseline: matching rows become visible to the rule's audience, non-matching stay hidden", + "since": "v15.1", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": [ + "a member in the rule's audience (position / unit_and_subordinates)", + "a member NOT in the audience", + "admin" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the shipped criteria sharing rules + sys_business_unit tree (examples/app-showcase/src/security/)", + "runtime-assigned exec/manager/Field-Ops personas (as access-security.scope-depth-asymmetry already provisions)" + ], + "knownGaps": [ + "the owner-based rule is [experimental] and skipped per the liveness ledger — exclude it; criteria + position/unit audiences are the live surface" + ] + }, + "variants": [ + "criteria rule, position audience", + "criteria rule, unit_and_subordinates audience", + "manual one-off share via the record-shares route" + ], + "steps": [ + "boot showcase isolated; assign the exec/manager/Field-Ops positions to distinct runtime members", + "as admin create a MATCHING probe record (satisfies a rule's criteria, e.g. a high-value / red project or a matching inquiry) and a NON-MATCHING probe on the same private-OWD object", + "as the audience member: GET the object list and GET each probe by id", + "as a member OUTSIDE the audience: same reads", + "read sys_record_share for the matching record; run /security/explain for the audience member + matching record", + "manual-share the non-matching record to a specific user via the record-shares route; that user re-reads", + "§A13: as admin edit a materialized share, restart the server, re-read (seed-not-clobber)" + ], + "acceptance": [ + { + "clause": "the audience member reads the MATCHING record (list + by-id) though the object OWD is private — the rule widened access", + "oracle": "api", + "verify": "audience member's list contains the matching id and the by-id GET is 200", + "evidence": "the reads" + }, + { + "clause": "the NON-matching record stays invisible to the same audience member, and the matching record stays invisible to a member outside the audience — widening is scoped, both sides", + "oracle": "api", + "verify": "non-matching by-id GET is denied for the audience member; matching by-id GET is denied for the outsider", + "evidence": "the four reads" + }, + { + "clause": "the widening is materialized: a sys_record_share row exists for the matching record and the explain output names the SHARING layer as the deciding grant (not OWD, not position)", + "oracle": "api", + "verify": "sys_record_share read + /security/explain attribution", + "evidence": "the row + explain" + }, + { + "clause": "a manual one-off share grants exactly the target user and no one else (both sides)", + "oracle": "api", + "verify": "target reads the record; a third user still cannot", + "evidence": "the two reads" + }, + { + "clause": "§A13 — an admin edit to a materialized/seeded share survives a restart (the seed does not clobber it, #2909)", + "oracle": "api", + "verify": "post-restart re-read shows the admin's edit intact", + "evidence": "before/after-restart reads" + } + ], + "negative": [ + "a non-matching record becoming visible to the audience (over-broad rule), or a matching record leaking to an outsider, is a FAIL; a share silently dropped on restart is the #2909 regression" + ], + "traps": [ + "wrong-persona", + "seed-data-thin" + ], + "source": [ + "examples/app-showcase/src/security/ (criteria rules + sys_business_unit tree)", + "content/docs/capabilities/permissions.mdx (layer 3 sharing), showcase_tour_security ('Widening')", + "docs/plans/release-15.1-test-plan.md §A13 (#2909)", + "packages/plugins/plugin-sharing (sys_record_share materialization, ADR-0055)" + ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "new — auth-boundary sweep over every mounted family, grounded in the #2567/#5519 conformance rows and their dogfood pin", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — THE marquee gap: sharing rules (criteria → materialized sys_record_share) had zero behavioral coverage though showcase ships stock fixtures; owd-sharing-matrix only did baselines", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "access-security.record-share-grant-revoke", + "title": "Per-record manual shares grant, scope, and revoke access on a private-OWD record; rule evaluate reconciles the audience", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": [ + "member A (owner of the private-OWD probe note)", + "member B (the grantee — baseline only, no relation to A)", + "member C (a third baseline member — the isolation contrast)", + "admin (drives the rule evaluate + system-context re-reads)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_private_note (sharingModel: private — member_default grants create/read/edit, so a non-owner sees nothing without a share)", + "three fresh sign-ups (A/B/C) whose sys_user ids are resolved via a system-context read", + "the seeded criteria sharing rule share_red_projects_with_execs on showcase_project + at least one red-health showcase_project (examples/app-showcase/src/security/sharing-rules.ts) for the evaluate clause" + ] + }, + "steps": [ + "boot showcase isolated (own port + file DB, dogfood skill §0); admin session + members A/B/C (fresh emails); resolve their sys_user ids system-side", + "as A: POST /api/v1/data/showcase_private_note {\"title\": \"A share probe\"} — capture the id", + "as B: GET /api/v1/data/showcase_private_note/ — the PRE-grant baseline (private OWD, B is not owner → must be refused)", + "as A: POST /api/v1/data/showcase_private_note//shares {\"recipientType\": \"user\", \"recipientId\": \"\", \"accessLevel\": \"read\"} — capture the 201 sys_record_share row (source 'manual')", + "as B: GET the note by id AND GET /api/v1/data/showcase_private_note (list) — the grant must make it visible both ways", + "as C: GET the note by id — still refused (the grant named B, not everyone)", + "as A: GET /api/v1/data/showcase_private_note//shares (management-gated list) — the manual grant is present", + "as B (a non-manager on A's note): POST /api/v1/data/showcase_private_note//shares granting themselves — must be refused (creating a share is not a reader's power, ADR-0111 D5)", + "as A: DELETE /api/v1/data/showcase_private_note//shares/ → 204; then as B: GET the note by id again — refused once more", + "revoke-scope probe: attempt DELETE of the same shareId through a DIFFERENT record's path (/data/showcase_private_note//shares/) — must be refused (the URL's object/id is the revoke scope, ADR-0111 D4)", + "as admin: POST /api/v1/sharing/rules/share_red_projects_with_execs/evaluate — capture the reconcile result; read sys_record_share for the matched red project" + ], + "acceptance": [ + { + "clause": "PRE-grant isolation holds: B's by-id GET of A's private note is non-2xx before any share exists (the baseline the grant then widens)", + "oracle": "api", + "verify": "GET /api/v1/data/showcase_private_note/ as B answers non-200 (403 PERMISSION_DENIED or a not-found-shaped denial) BEFORE the POST /shares", + "evidence": "the pre-grant read" + }, + { + "clause": "the manual grant lands as a sys_record_share row: POST .../shares answers 201 with recipient_type 'user', recipient_id B, access_level 'read', source 'manual', granted_by A", + "oracle": "api", + "verify": "POST /api/v1/data/showcase_private_note//shares status 201; the returned/re-read sys_record_share row carries those fields (packages/plugins/plugin-sharing/src/objects/sys-record-share.object.ts)", + "evidence": "the POST response + a system-context sys_record_share read" + }, + { + "clause": "the grant widens B's read BOTH ways and stays scoped: after the grant B reads the note by id (200) AND it appears in B's list, while C (ungranted) still cannot read it by id", + "oracle": "api", + "verify": "B by-id GET 200 + B list contains the id; C by-id GET non-200 and C list excludes it", + "evidence": "B's two reads + C's two reads" + }, + { + "clause": "the shares list is management-gated: A (owner/manager) lists the record's shares, but B (visible-but-not-manager) POSTing a share is refused 403 (or 404 when the record is invisible) — reading a record does not confer re-share authority (ADR-0111 D5)", + "oracle": "api", + "verify": "A GET .../shares 200 with the grant present; B POST .../shares >=400 (403 visible-not-manager / 404 invisible)", + "evidence": "A's list + B's refused POST" + }, + { + "clause": "revoke retracts access: DELETE .../shares/:shareId answers 204 and B's next by-id GET is non-2xx again — access tracks the grant lifecycle, not a cached decision", + "oracle": "api", + "verify": "DELETE 204; B by-id GET after revoke non-200; a system-context sys_record_share read shows the row gone", + "evidence": "DELETE trace + B's post-revoke read" + }, + { + "clause": "revoke is record-scoped: revoking the share id through a different record's path is refused — a share can only be revoked through the record it belongs to (ADR-0111 D4)", + "oracle": "api", + "verify": "DELETE /data/showcase_private_note//shares/ answers >=400 and the share still exists (re-read)", + "evidence": "the mis-scoped DELETE trace + survival read" + }, + { + "clause": "rule evaluate reconciles the audience: POST /sharing/rules/share_red_projects_with_execs/evaluate returns {ruleId, matchedRecords>=1, grantsCreated/grantsUpdated} and a sys_record_share row exists for the matched red project with source 'rule' and source_id the rule name", + "oracle": "api", + "verify": "the SharingRuleEvaluationResult body (packages/plugins/plugin-sharing/src/sharing-rule-service.ts evaluateRule) + the materialized rule-sourced share row", + "evidence": "evaluate response + the sys_record_share read" + } + ], + "negative": [ + "a revoked share whose record still reads 200 for B, or a grant that leaks to C, is a FAIL even though the manual grant looked correct — access must equal the live grant set", + "a non-manager (B) successfully POSTing a share on A's note is the ADR-0111 D5 authority gap — a 2xx there is a FAIL" + ], + "traps": [ + "wrong-persona" + ], + "source": [ + "packages/rest/src/rest-route-ledger.ts (record-shares family: GET/POST /data/:object/:id/shares, DELETE .../:shareId; sharing-rules family evaluate)", + "packages/rest/src/rest-server.ts (registerRecordShareEndpoints ~L7246-7331; registerSharingRuleEndpoints evaluate ~L7477-7493)", + "packages/plugins/plugin-sharing/src/objects/sys-record-share.object.ts (recipient_type/recipient_id/access_level/source fields)", + "packages/plugins/plugin-sharing/src/sharing-rule-service.ts (evaluateRule → SharingRuleEvaluationResult)", + "examples/app-showcase/src/security/sharing-rules.ts (share_red_projects_with_execs seeded criteria rule)", + "ADR-0111 D1/D4/D5" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "new — per-record manual share grant/scope/revoke lifecycle on showcase_private_note plus rule-evaluate reconcile, grounded in the ADR-0111 record-shares routes", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "access-security.audit-log-browser", + "title": "The audit-log browser surfaces attributable events over sys_audit_log with correct actor/object, filters, and a before/after payload drawer", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": [ + "admin (drives the /system/audit-log page and the delete + settings write)", + "a fresh member (generates the attributable login event)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "sys_audit_log (plugin-audit; enable.apiMethods = get + list only — writes happen only through internal system hooks)", + "the console audit-log page at /system/audit-log (objectui apps/console/src/pages/system/AuditLogPage.tsx → /api/v1/data/sys_audit_log)", + "a deletable seeded record (e.g. a showcase_task) and a writable settings namespace for the config_change event" + ] + }, + "steps": [ + "boot showcase with the console; admin session", + "generate 3 attributable events with distinct action/actor/object: (a) a fresh member SIGNS IN (action 'login', actor = the member); (b) as admin DELETE a showcase_task via /api/v1/data/showcase_task/ (action 'delete', object_name showcase_task, record_id set); (c) write a setting (action 'config_change')", + "open /system/audit-log; wait for the table to settle; screenshot", + "locate all three events in the page and read their action / object / actor cells", + "set the Action filter to 'delete' and confirm only the delete event remains; capture the re-issued /api/v1/data/sys_audit_log request with its $filter", + "click the delete row → the side drawer opens; screenshot the Before (old_value) / After (new_value) JSON panels", + "cross-check the API twin directly: GET /api/v1/data/sys_audit_log?$filter=... for each of the three actions and compare actor/object/action to the page", + "attempt to forge the trail: POST and PATCH /api/v1/data/sys_audit_log — both must be refused (get+list only)" + ], + "acceptance": [ + { + "clause": "all three ops produce audit rows with the correct action, actor and target: login→action 'login' attributed to the member; delete→action 'delete' with object_name showcase_task + record_id; settings write→action 'config_change'", + "oracle": "api", + "verify": "GET /api/v1/data/sys_audit_log returns the three rows; action/actor(user_id)/object_name/record_id match what each op did (fields per packages/plugins/plugin-audit/src/objects/sys-audit-log.object.ts)", + "evidence": "the three audit rows" + }, + { + "clause": "the browser renders those same rows: after a screenshot confirms the table painted, the DOM rows carry the same action/actor/object the API returned — the page shows server truth, not a recomputation", + "oracle": "dom", + "verify": "post-screenshot, the three rows' Action/Object/Actor cells equal the API values (read the DOM only after render is confirmed — hydration-race)", + "evidence": "screenshot + the row DOM read" + }, + { + "clause": "the Action filter narrows to exactly one event, and the narrowing is server-side", + "oracle": "network", + "verify": "setting Action=delete re-issues GET /api/v1/data/sys_audit_log with $filter carrying action=delete; the table then shows only the delete event", + "evidence": "the filtered request + the single-row table" + }, + { + "clause": "the drawer shows the change payload as Before/After JSON — old_value and new_value pretty-printed, not just a label (recorded faithfully as a two-panel before/after, NOT a merged semantic diff)", + "oracle": "screenshot", + "verify": "the row drawer renders old_value under 'Before' and new_value under 'After' as JSON where the event carries them", + "evidence": "the drawer screenshot annotated 'before/after panels, not a diff'" + }, + { + "clause": "the API twin reconciles with the page: GET /api/v1/data/sys_audit_log returns the same three events with matching actor/object — a page row without a backing API row (or vice versa) is a FAIL", + "oracle": "api", + "verify": "field-by-field compare of the page's three rows against the /api/v1/data/sys_audit_log bodies", + "evidence": "API list vs page rows" + }, + { + "clause": "the trail is append-only via the data API: POST and PATCH /api/v1/data/sys_audit_log are refused (enable.apiMethods = get + list) — an actor cannot forge or edit their own audit record", + "oracle": "api", + "verify": "POST and PATCH both answer >=400 (method not permitted for the object); a follow-up list shows no forged row", + "evidence": "the two write traces + the list" + } + ], + "negative": [ + "a login / delete / config_change that leaves NO audit row is the #3415-class silent-seed/hook failure — a FAIL against the audit hook, not a fixture block", + "a successful POST/PATCH to sys_audit_log via the data API is a FAIL — append-only must hold at the server, not only in the read-only page" + ], + "traps": [ + "hydration-race", + "stale-console-bundle" + ], + "source": [ + "packages/plugins/plugin-audit/src/objects/sys-audit-log.object.ts (action enum, actor/user_id/object_name/record_id/old_value/new_value fields, enable.apiMethods get+list)", + "packages/plugins/plugin-audit/src/audit-plugin.ts (nav_audit_logs → sys_audit_log)", + "objectui apps/console/src/pages/system/AuditLogPage.tsx (/api/v1/data/sys_audit_log, $filter/$orderby/$top/$skip, Before/After drawer)", + "objectui apps/console/src/AppContent.tsx (route system/audit-log)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "new — audit-log browser over sys_audit_log: attributable events, server-side filter, before/after payload drawer, API cross-check, append-only guard", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "access-security.owd-save-gate", + "title": "OWD save gate: external ≤ internal on every object write, and a packaged object's OWD can only be tightened at runtime — Studio inline AND server-side", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": [ + "admin (drives the Studio designer and the direct meta PUTs)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase declares externalSharingModel on shipped objects: showcase_announcement (external private ≤ internal public_read) and showcase_account (external public_read ≤ internal public_read_write)", + "a writable/draft object name for the R2 server probe (author via PUT /api/v1/meta/object/qa_owd_probe?mode=draft — no shipped object is mutated)", + "the Studio object designer Settings tab (objectui ObjectSettingsPanel / PackageOwdOverviewPanel), which computes isExternalWider inline" + ], + "knownGaps": [ + "runtime EVALUATION of externalSharingModel against external principals is liveness `planned` (#2696) — this item tests the AUTHORING/save gate (external ≤ internal at write; packaged tighten-only), NOT external-principal enforcement; do not tick an external-principal read/write result", + "R1 (owd_widening_forbidden) is only reachable via the OS_METADATA_WRITABLE=object escape hatch; on a STOCK deploy a runtime meta PUT to a packaged object is refused earlier by the writable-package gate (WRITABLE_PACKAGE_REQUIRED) — the run must record WHICH layer answered the 4xx" + ] + }, + "steps": [ + "boot showcase with the console; admin session", + "Studio inline (browser): open the object designer Settings for a writable/draft object; set sharingModel=private and externalSharingModel=public_read; screenshot — the panel must flag the D11 external-wider violation inline (owd-sharing.ts isExternalWider) and not offer a clean save", + "server R2 reject: PUT /api/v1/meta/object/qa_owd_probe?mode=draft with sharingModel:'private' + externalSharingModel:'public_read' — capture the 4xx + error code", + "server R2 admit: PUT the same draft with externalSharingModel:'private' (≤ private) — capture the 2xx (the gate orders, it does not blanket-deny)", + "packaged tighten (R1): PUT /api/v1/meta/object/showcase_announcement widening sharingModel private→public_read_write (or externalSharingModel private→public_read) — capture the 4xx AND its code (WRITABLE_PACKAGE_REQUIRED on stock; owd_widening_forbidden under OS_METADATA_WRITABLE=object)", + "GET /api/v1/meta/object/showcase_announcement before and after the denied PUT — confirm byte-identical", + "cross-check the shipped declarations: GET /api/v1/meta/object/showcase_account and showcase_announcement — both satisfy external ≤ internal" + ], + "acceptance": [ + { + "clause": "Studio flags external-wider inline: with sharingModel=private + externalSharingModel=public_read the Settings panel shows the D11 violation and does not present a clean save", + "oracle": "screenshot", + "verify": "after the designer settles, the panel surfaces the external-wider warning (owd-sharing.ts isExternalWider / ObjectSettingsPanel externalWider) — a screenshot confirms it before any DOM read", + "evidence": "the designer screenshot" + }, + { + "clause": "server R2 rejects external > internal on ANY object write: the writable-object PUT with externalSharingModel wider than sharingModel answers 4xx (403) code owd_external_wider", + "oracle": "api", + "verify": "PUT /api/v1/meta/object/qa_owd_probe?mode=draft status >=400 and body code 'owd_external_wider' (packages/plugins/plugin-security/src/object-posture-gate.ts R2)", + "evidence": "the PUT trace" + }, + { + "clause": "server R2 admits external ≤ internal: the same object with externalSharingModel no wider than sharingModel saves 2xx — the gate keys on the width ordering, not on the key's presence", + "oracle": "api", + "verify": "PUT with externalSharingModel:'private' answers 2xx and a follow-up GET shows the value", + "evidence": "the PUT trace + read" + }, + { + "clause": "a packaged object's OWD cannot be widened at runtime: the widening PUT on showcase_announcement answers 4xx with a ledgered code — WRITABLE_PACKAGE_REQUIRED on the stock deploy, or owd_widening_forbidden under the OS_METADATA_WRITABLE=object escape hatch — recorded with WHICH layer answered", + "oracle": "api", + "verify": "PUT /api/v1/meta/object/showcase_announcement (widened) status >=400 and code ∈ {WRITABLE_PACKAGE_REQUIRED, owd_widening_forbidden}", + "evidence": "the PUT trace + the code + the answering-layer note" + }, + { + "clause": "the denied widening is inert: GET /api/v1/meta/object/showcase_announcement is byte-identical before and after the refused PUT", + "oracle": "api", + "verify": "the two metadata reads match on sharingModel + externalSharingModel", + "evidence": "the before/after reads" + }, + { + "clause": "the shipped app passes its own gate: showcase_account (external public_read ≤ internal public_read_write) and showcase_announcement (external private ≤ internal public_read) both satisfy external ≤ internal", + "oracle": "api", + "verify": "GET both objects' metadata; externalSharingModel width ≤ sharingModel width for each (OWD_WIDTH private\"} — capture the 422 SHARING_NOT_ENABLED that proves the block (no showcase object opts in)", + "with a publicSharing-enabled fixture object F (redactFields declared): as a member who can see a record r of F, POST /api/v1/share-links {object:F, recordId:r, redactFields?, audience?, password?, expiresAt?} — capture the 201 {token}", + "anon GET /api/v1/share-links//resolve — capture the 200 {record, link, redactFields}; confirm every field in F.publicSharing.redactFields ∪ link.redact_fields is absent from record", + "mint a password-gated link; anon resolve WITHOUT ?password / x-share-password header → 401; with a WRONG password → 401; with the correct password → 200", + "mint audience:'signed_in'; anon resolve → 401 SIGN_IN_REQUIRED; mint audience:'email' and resolve with an email OFF the allowlist → refused", + "DELETE /api/v1/share-links/ (revoke); anon resolve → 410 EXPIRED_OR_REVOKED; mint a short-expiry link and, after it expires, resolve → 410 — the record must never appear", + "delete the shared record r, then anon resolve the still-live token → 410 RECORD_GONE (fail-closed, #5190)", + "GET /api/v1/share-links?object=F&recordId=r as the minter vs a SECOND member — the list returns only the caller's own links" + ], + "acceptance": [ + { + "clause": "mint requires the per-object opt-in: POST /share-links on an object WITHOUT publicSharing.enabled answers 422 SHARING_NOT_ENABLED — link-sharing is opt-in per object (this is exactly why stock showcase blocks the rest of this item)", + "oracle": "api", + "verify": "POST /api/v1/share-links {object: any stock showcase object} → 422 code SHARING_NOT_ENABLED (packages/plugins/plugin-sharing/src/share-link-service.ts getPolicy gate)", + "evidence": "the 422 trace" + }, + { + "clause": "resolve renders the record MINUS redactFields: anon GET /:token/resolve returns 200 with the record, and every field in the object's publicSharing.redactFields ∪ the per-link redact_fields is stripped before it leaves the server", + "oracle": "api", + "verify": "the resolve body's record omits the redaction set; body also carries link + redactFields (packages/runtime/src/domains/share-links.ts applyRedaction)", + "evidence": "the resolve body" + }, + { + "clause": "the password gate holds both sides: no/blank password → 401 NEEDS_PASSWORD; wrong password → 401 WRONG_PASSWORD; correct password → 200 — the token alone is insufficient when a password is set", + "oracle": "api", + "verify": "the three resolve traces with the declared codes (share-links.ts sendErr NEEDS_PASSWORD/WRONG_PASSWORD)", + "evidence": "the three traces" + }, + { + "clause": "audience gating holds: a signed_in-audience link answers 401 SIGN_IN_REQUIRED for an anonymous caller; an email-audience link refuses an email off the allowlist (resolveToken returns null → named refusal, never the record)", + "oracle": "api", + "verify": "signed_in resolve → 401 SIGN_IN_REQUIRED; email resolve with a non-allowlisted email → refused", + "evidence": "the two traces" + }, + { + "clause": "revoke / expiry / record-gone name the refusal, NEVER the record: after DELETE, past expiresAt, or record deletion, resolve answers 410 (EXPIRED_OR_REVOKED / RECORD_GONE) — a dead token must never leak the row (#5190 fail-closed)", + "oracle": "api", + "verify": "post-revoke, post-expiry, and post-record-delete resolves each answer 410 with no record in the body", + "evidence": "the three traces" + }, + { + "clause": "the list is caller-scoped: GET /share-links returns only links created_by the caller; a second member cannot enumerate the minter's tokens even with a guessed recordId", + "oracle": "api", + "verify": "minter's list contains the token; the second member's list for the same object/recordId excludes it (share-links.ts createdBy pin)", + "evidence": "both list bodies" + } + ], + "negative": [ + "a resolve that returns the record after revoke/expiry, that includes a redactField, or that leaks another user's tokens in the list, is a FAIL", + "the /:token/messages branch is ai_conversations-only (Cloud/EE) — a knownGap, not a stock clause; do not tick it on open-framework showcase" + ], + "variants": [ + "audience link_only", + "audience signed_in", + "audience email (allowlist)", + "password-gated", + "redactFields stripped", + "revoked", + "expired", + "record-gone (fail-closed)" + ], + "traps": [ + "wrong-persona" + ], + "source": [ + "packages/runtime/src/domains/share-links.ts (resolve/create/list/revoke; 401 NEEDS_PASSWORD/WRONG_PASSWORD/SIGN_IN_REQUIRED, 410 EXPIRED_OR_REVOKED/RECORD_GONE, applyRedaction)", + "packages/plugins/plugin-sharing/src/share-link-service.ts (getPolicy publicSharing gate → 422 SHARING_NOT_ENABLED, resolveToken audience/password/expiry, #5190 recordStillExists fail-closed, list createdBy scoping)", + "packages/runtime/src/route-ledger.ts (share-links rows incl. public resolve/messages)", + "packages/plugins/plugin-sharing/src/objects/sys-share-link.object.ts", + "ADR-0047, ADR-0111 D8, #5190" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "new — share-link capability tokens (ADR-0047): resolve-minus-redactFields, password/audience gates, fail-closed revoke/expire/record-gone, caller-scoped list; blocked(fixture) because no showcase object opts into publicSharing, messages half split as a Cloud/EE knownGap", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "access-security.suggested-binding-loop", + "title": "Suggested audience bindings reconcile, confirm materializes the anchor binding, dismiss removes it; bad states 404/409/400 and non-admins are refused", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "api", + "personas": [ + "tenant admin (the only principal the surface serves)", + "a plain member (the deny contrast)", + "anonymous (the unconditional-deny contrast)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "sys_audience_binding_suggestion (plugin-security) + the reconcile at boot/list", + "showcase's isDefault permission set showcase_member_default (examples/app-showcase/src/security/permission-sets.ts) — the one declared install-time suggestion" + ], + "knownGaps": [ + "stock showcase AUTO-BINDS its only isDefault set (everyone → showcase_member_default) at boot (examples/app-showcase/src/security/bind-position-sets.ts docblock: the security plugin auto-binds the app's isDefault/fallbackPermissionSet to everyone), so the reconcile marks that suggestion 'confirmed (observed)', NOT pending — there is no PENDING suggestion to confirm/dismiss on stock. The list/reconcile, status-filter, idempotent-409, 404, non-admin-403 and anon-401 clauses run against the confirmed row; the confirm-a-PENDING and dismiss-a-PENDING clause needs a scratch package that suggests a not-auto-bound binding → knownGap until that fixture lands" + ] + }, + "steps": [ + "boot showcase with the console; admin session", + "GET /api/v1/security/suggested-bindings — capture the reconciled list; locate the showcase_member_default → everyone suggestion and its status", + "GET with ?status=confirmed and ?status=pending — confirm the filter narrows and the member_default suggestion reports 'confirmed' (auto-bound at boot)", + "GET with ?status=garbage — capture the 400 naming the accepted values", + "POST /api/v1/security/suggested-bindings//confirm — capture the 409 SUGGESTION_STATE (confirm is not re-runnable on a settled row)", + "POST /api/v1/security/suggested-bindings/does-not-exist/confirm — capture the 404 SUGGESTION_NOT_FOUND", + "as a plain member: GET the list AND POST a confirm — both must be refused 403 (tenant-admin pre-gate)", + "anon (no auth): GET the list — capture the 401 UNAUTHENTICATED", + "(knownGap) PENDING loop: seed/install a package suggesting a not-auto-bound binding so a 'pending' row appears; POST /confirm materializes the anchor binding under the caller's context (verify via /api/v1/security/explain or an access probe); POST /dismiss records the 'no' and drops it from pending; re-list reflects the transition" + ], + "acceptance": [ + { + "clause": "the list reconciles declarations against live bindings: GET /security/suggested-bindings returns showcase's isDefault suggestion, and the reconcile marks it 'confirmed' because everyone → showcase_member_default is already bound at boot (a binding-present suggestion is observed-confirmed, never pending)", + "oracle": "api", + "verify": "the list body carries the member_default suggestion with status 'confirmed' (packages/plugins/plugin-security/src/suggested-audience-bindings.ts syncAudienceBindingSuggestions)", + "evidence": "the reconciled list" + }, + { + "clause": "the status filter is validated: ?status=confirmed|pending|dismissed narrow the set, while ?status=garbage answers 400 naming the accepted values — an unknown filter is not silently an empty list", + "oracle": "api", + "verify": "the three filtered lists + the 400 body (packages/runtime/src/domains/security.ts isSuggestionStatus guard)", + "evidence": "the filtered lists + the 400" + }, + { + "clause": "confirm on a settled row is a 409: POST ...//confirm answers 409 SUGGESTION_STATE — confirm is idempotent-safe, never a double-bind", + "oracle": "api", + "verify": "the confirm trace status 409 code SUGGESTION_STATE (SuggestionStateError)", + "evidence": "the trace" + }, + { + "clause": "a bad suggestion id is a 404: POST .../does-not-exist/confirm|dismiss answers 404 SUGGESTION_NOT_FOUND", + "oracle": "api", + "verify": "the trace status 404 code SUGGESTION_NOT_FOUND (SuggestionNotFoundError)", + "evidence": "the trace" + }, + { + "clause": "the whole surface is tenant-admin-only: a plain member's list AND confirm both answer 403 — the read is as gated as the write (ADR-0066 pre-gate)", + "oracle": "api", + "verify": "member GET list and POST confirm both >=400 (403)", + "evidence": "both member traces" + }, + { + "clause": "anonymous is denied unconditionally: an unauthenticated GET answers 401 UNAUTHENTICATED (this admin seam never honoured a deny opt-out, #2567/#3963)", + "oracle": "api", + "verify": "anon GET /security/suggested-bindings status 401 code UNAUTHENTICATED", + "evidence": "the anon trace" + }, + { + "clause": "(knownGap) confirm materializes a PENDING binding and dismiss removes it: given a fixture seeding a pending suggestion, POST /confirm creates the anchor binding under the CALLER's context (a follow-up /security/explain shows the new grant), and POST /dismiss records the no and drops it from pending", + "oracle": "api", + "verify": "pre/post /security/explain (or access probe) around confirm; the list transitions pending→confirmed and pending→dismissed", + "evidence": "explain before/after + the list transitions" + } + ], + "negative": [ + "a confirm that binds a high-privilege set onto everyone/guest must be refused by the D5/D9 audience-anchor gate (403), never silently accepted", + "a confirm running under the SYSTEM context rather than the caller's is the ADR-0090 D9 violation — the write must carry the admin's identity through the gates", + "the pending-loop clause is fixture-gapped (stock auto-binds the only suggestion) — do not tick confirm/dismiss-a-pending on stock showcase" + ], + "traps": [ + "wrong-persona" + ], + "source": [ + "packages/runtime/src/domains/security.ts (GET list / POST :id/confirm|dismiss; 400 unknown-status, 401 anon, 403/404/409 typed-error mapping)", + "packages/plugins/plugin-security/src/suggested-audience-bindings.ts (syncAudienceBindingSuggestions convergent reconcile; SuggestionNotFoundError 404, SuggestionStateError 409; confirm under caller context)", + "packages/plugins/plugin-security/src/objects/sys-audience-binding-suggestion.object.ts", + "packages/rest/src/rest-route-ledger.ts (security suggested-bindings rows)", + "examples/app-showcase/src/security/bind-position-sets.ts + permission-sets.ts (isDefault auto-bind)", + "ADR-0090 D5/D9/D12, #2567/#3963" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "new — suggested-binding admin loop (ADR-0090 D5/D9): reconcile/list, status-filter validation, idempotent-409, 404, admin-only + anon-deny; confirm/dismiss-a-pending carried as a knownGap because stock auto-binds its only isDefault suggestion", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "access-security.sharing-rule-authoring-ui", + "title": "Authoring a sharing rule in Setup materializes matching grants for exactly the audience; deleting the rule retracts them", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": [ + "admin (authors + deletes the rule in Setup)", + "a member in the rule's audience (gains exactly the matching rows)", + "a member OUTSIDE the audience (the scoping contrast)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the Setup 'Sharing Rules' surface (objectui plugin-sharing sharing-plugin.ts nav_sharing_rules → sys_sharing_rule record form, requiredPermissions manage_platform_settings) OR the POST /api/v1/sharing/rules authoring endpoint", + "a private-OWD object with authorable matching/non-matching rows (showcase_project — health field) and a position to receive the grant (e.g. auditor or a scratch position)" + ], + "knownGaps": [ + "authoring a rule in Setup issues a direct sys_sharing_rule insert (packages/spec/src/security/sharing.zod.ts) — use a criteria DISTINCT from the seeded red-project rules (e.g. record.health == 'green') so this item does not overlap access-security.sharing-rules-widen, which tests the SEEDED rules' enforcement" + ] + }, + "steps": [ + "boot showcase with the console; admin session", + "author a NEW criteria sharing rule via Setup → Sharing Rules (create a sys_sharing_rule record) OR POST /api/v1/sharing/rules: object showcase_project, condition record.health == 'green' (distinct from the seeded red rules), accessLevel 'read', sharedWith {type:'position', value:''} — capture the 2xx", + "as admin: seed a MATCHING probe (a green-health showcase_project owned by someone else) and a NON-MATCHING probe (a red-health project)", + "as the audience persona (holding that position): GET /api/v1/data/showcase_project (list) + GET each probe by id", + "as a member OUTSIDE the audience: the same reads", + "read sys_record_share for the matching project — a row with source 'rule', source_id the new rule name", + "negative-authoring probe: POST /api/v1/sharing/rules with a missing/empty criteria — capture the rejection (defineRule refuses a match-all, #3896)", + "delete the rule via Setup (delete the sys_sharing_rule record) OR DELETE /api/v1/sharing/rules/:idOrName", + "as the audience persona: re-read the matching project by id — refused again; re-read sys_record_share — the rule-sourced grant is gone", + "screenshot the Setup Sharing Rules list before authoring, after authoring, and after delete" + ], + "acceptance": [ + { + "clause": "authoring lands a rule: creating the criteria rule (Setup record form or POST /sharing/rules) answers 2xx and the rule reads back via GET /api/v1/sharing/rules/:idOrName", + "oracle": "api", + "verify": "the create response 2xx + the getRule read of the same idOrName", + "evidence": "create + read traces" + }, + { + "clause": "a match-all criteria is refused at authoring: a rule with a missing/empty criteria answers 400 (defineRule refuses match-all, #3896) — a typo'd predicate cannot silently share every record", + "oracle": "api", + "verify": "POST /sharing/rules with empty criteria → 400 VALIDATION_FAILED naming the field", + "evidence": "the rejection" + }, + { + "clause": "the rule materializes grants for MATCHING rows only: a sys_record_share (source 'rule', source_id the rule name) exists for the matching green project and none for the non-matching red project", + "oracle": "api", + "verify": "system-context sys_record_share reads for both probes", + "evidence": "the two share reads" + }, + { + "clause": "the audience persona gains exactly the matching rows, both sides: the audience persona reads the matching project (list + by-id) but not the non-matching; a member OUTSIDE the audience reads neither — widening is scoped", + "oracle": "api", + "verify": "audience: matching by-id 200 + present in list, non-matching by-id non-200; outsider: both by-id non-200", + "evidence": "the four+ reads" + }, + { + "clause": "delete retracts: deleting the rule removes its materialized sys_record_share grants and the audience persona's next by-id read of the matching project is refused again", + "oracle": "api", + "verify": "post-delete: sys_record_share for the matching project has no rule-sourced row; audience by-id GET non-200", + "evidence": "post-delete share read + persona read" + }, + { + "clause": "the Setup surface reflects the rule lifecycle: after authoring the Sharing Rules list shows the new rule; after delete it is gone — the UI renders the same rows the API serves", + "oracle": "screenshot", + "verify": "before/after/after-delete screenshots of the Setup Sharing Rules list", + "evidence": "the three screenshots" + } + ], + "negative": [ + "a rule that shares the NON-matching (red) project, or grants that survive the rule's deletion (orphaned sys_record_share), is a FAIL", + "do not overlap access-security.sharing-rules-widen: that item verifies the SEEDED rules' enforcement — this item authors a DISTINCT criteria in-run and verifies authoring + retraction" + ], + "traps": [ + "wrong-persona", + "hydration-race" + ], + "source": [ + "packages/rest/src/rest-server.ts (registerSharingRuleEndpoints ~L7345-7494: list/create/get/delete)", + "packages/spec/src/security/sharing.zod.ts (criteria rule authoring + match-all refusal #3896)", + "packages/plugins/plugin-sharing/src/objects/sys-sharing-rule.object.ts + sys-record-share.object.ts", + "objectui packages/plugins/plugin-sharing/src/sharing-plugin.ts (nav_sharing_rules Setup nav → sys_sharing_rule)", + "cross-ref access-security.sharing-rules-widen (seeded-rule enforcement), ADR-0058 D3, ADR-0111 D6" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "new — sharing-rule AUTHORING loop (Setup create → materialized matching grants → delete retracts), distinct from sharing-rules-widen which tests the seeded rules' enforcement", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "access-security.permission-matrix-edit-loop", + "title": "Editing the permission matrix and publishing flips an affected persona's live API access; revoke flips it back; assign/unassign moves access", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": [ + "admin (edits the matrix + publishes)", + "member M (holds the edited set — the persona whose API access flips)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "a WRITABLE (draft/app-local) permission set S — authored in-run or an unlocked set (a locked package set is the readonly-package-locks-studio case, not this one)", + "member M granted S via a sys_user_permission_set row", + "the console Permission Matrix editor (objectui PermissionMatrixEditor.tsx, type=permission → client.save() → PUT /api/v1/meta/permission/, then publish)" + ] + }, + "steps": [ + "boot showcase with the console; admin session; provision member M granted a writable set S that WITHHOLDS a verb on an object (e.g. delete:false on showcase_task)", + "baseline: as M, DELETE /api/v1/data/showcase_task/ — must be refused 403 (the withheld verb)", + "in the console Permission Matrix editor for S, flip delete ON for showcase_task; save (draft) then publish — capture PUT /api/v1/meta/permission/S and POST /api/v1/meta/permission/S/publish", + "as M: DELETE /api/v1/data/showcase_task/ — must now be 2xx (access flipped)", + "flip delete back OFF in the matrix; publish; as M: DELETE again — must be refused 403", + "field-level half: flip an FLS bit (a showcase_project field editable:false→true) in the lower matrix; publish; as M PATCH that field — the write flips accordingly", + "assign/unassign: grant M a SECOND permission set (insert sys_user_permission_set) — M gains its verbs; remove the row — the access retracts", + "re-open the editor for S and read the checkbox state for the flipped verb" + ], + "acceptance": [ + { + "clause": "baseline withheld verb is denied: before the edit, M's DELETE on the object answers 403 PERMISSION_DENIED (S withholds it)", + "oracle": "api", + "verify": "DELETE /api/v1/data/showcase_task/ as M → 403 PERMISSION_DENIED", + "evidence": "the baseline trace" + }, + { + "clause": "the matrix edit saves and publishes: flipping the verb in the console and publishing answers 2xx on PUT /api/v1/meta/permission/S and the publish call, and the published set carries the new verb", + "oracle": "api", + "verify": "PUT + POST .../publish both 2xx; GET /api/v1/meta/permission/S (published) shows delete:true on showcase_task", + "evidence": "PUT + publish + GET" + }, + { + "clause": "access flips ON after publish: M's identical DELETE now answers 2xx and the row is gone — the persona's live API access tracks the PUBLISHED matrix", + "oracle": "api", + "verify": "DELETE as M → 2xx + a follow-up read shows the row absent", + "evidence": "trace + re-read" + }, + { + "clause": "revoke flips it back: flipping the verb OFF and republishing returns M's DELETE to 403 — the edit loop is reversible", + "oracle": "api", + "verify": "post-republish DELETE as M → 403 PERMISSION_DENIED", + "evidence": "the trace" + }, + { + "clause": "assign/unassign moves access: granting M a second permission set widens M's API access to that set's verbs; removing the sys_user_permission_set row retracts it", + "oracle": "api", + "verify": "before/after traces of a verb the second set grants — 2xx while assigned, >=400 after unassign", + "evidence": "the before/after traces" + }, + { + "clause": "the editor reflects the published state: re-opening the matrix shows the flipped verb's checkbox in its published state — the editor renders the published set, not a stale draft", + "oracle": "dom", + "verify": "after a screenshot confirms the editor rendered, the verb's checkbox matches the last published value", + "evidence": "screenshot + checkbox DOM read" + } + ], + "negative": [ + "an edit that repaints the matrix but whose PUT/publish never lands (M's access unchanged) is a FAIL — the network + M's live access are the oracles, not the checkbox paint", + "enforcement flipping on the DRAFT alone (before publish) is wrong: access must key on the PUBLISHED set", + "cross-ref: enforcement correctness is access-security.crud-permission-matrix and the read-only-package lock is access-security.readonly-package-locks-studio — this item is the WRITE/edit loop only" + ], + "variants": [ + "object-verb flip (CRUD)", + "field-level R/W flip (FLS)", + "permission-set assign/unassign" + ], + "traps": [ + "stale-console-bundle", + "hydration-race", + "wrong-persona" + ], + "source": [ + "objectui packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx (type=permission editor; client.save → PUT /api/v1/meta/permission/; object CRUD+VAMA+lifecycle + field R/W)", + "packages/spec/src/security/permission.zod.ts (PermissionSetSchema)", + "packages/rest/src/rest-route-ledger.ts (PUT /api/v1/meta/:type/:name saveItem; POST .../publish publishItem)", + "examples/app-showcase/src/security/permission-sets.ts", + "cross-ref access-security.crud-permission-matrix + access-security.readonly-package-locks-studio, ADR-0090 D1/D5, ADR-0033" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "new — permission-matrix WRITE/edit loop: flip a verb in the console matrix → publish → affected persona's live API access flips, revoke flips back, assign/unassign moves access (enforcement + lock covered by sibling items)", + "ref": "claude/platform-test-checklist-ocwugl" + } ] } ] -} +} \ No newline at end of file diff --git a/docs/qa/platform-checklist/areas/ai.json b/docs/qa/platform-checklist/areas/ai.json index d0f02cd255..2142df55fb 100644 --- a/docs/qa/platform-checklist/areas/ai.json +++ b/docs/qa/platform-checklist/areas/ai.json @@ -1,187 +1,580 @@ { - "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md. NOTE: AI is deliberately absent from the showcase app (ADR-0063 — platform-owned), so most items here carry explicit fixture requirements instead of assuming showcase seeds.", + "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md. NOTE: AI is deliberately absent from the showcase app (ADR-0063 — platform-owned), so most items here carry explicit fixture requirements instead of assuming showcase seeds. BOUNDARY: the in-product agent runtime (`@objectstack/service-ai`) is Cloud/EE (cloud repo) — the OPEN framework serves the metadata surface, the MCP surface (packages/mcp), and honest 501s on /ai/** (route-ledger '* /ai/**' row); items below only assert what the open framework actually runs.", "area": "ai", "title": "AI — agents, tools, skills, MCP", "items": [ { "id": "ai.agent-tool-skill-metadata-roundtrip", - "title": "agent / tool / skill metadata kinds author, persist and list over the meta + REST surfaces", + "title": "agent / tool / skill metadata kinds author, persist and list over the meta surface — retired keys reject with prescriptions, the closed agent kind has no runtime write door", "since": "v15", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "api", "personas": ["admin"], "fixtures": { "app": "any", - "requires": ["a writable package to author into (AI artifacts are not seeded by the showcase — ADR-0063)"], + "requires": [ + "a writable package to author into (AI artifacts are not seeded by the showcase — ADR-0063); files land under the registered patterns *.agent.ts / *.tool.ts / *.skill.ts (packages/spec/src/kernel/metadata-plugin.zod.ts)", + "an admin session for the meta reads and a fresh unauthenticated client for the anonymous-deny half" + ], "knownGaps": ["no stock example app ships AI seeds; a minimal agent+tool+skill fixture config is needed for the full round-trip"] }, - "variants": ["agent", "tool", "skill"], + "variants": [ + "agent (AgentSchema requires name/label/role/instructions; surface defaults 'ask'; strictObject with aliases prompt→instructions, capabilities→skills …)", + "tool (ToolSchema .strict() requires name/label/description/parameters)", + "skill (SkillSchema requires name/label/tools; instructions optional; surface defaults 'ask')", + "tombstone probe: tool.permissions (removed spec 17, #3896)", + "tombstone probe: tool.active (removed spec 17, #3896)", + "tombstone probe: tool.category (removed spec 17, #3896)", + "tombstone probe: tool.builtIn (removed spec 17, #3896)", + "tombstone probe: tool.requiresConfirmation (removed 16.x, #3715 / ADR-0033 §2)", + "tombstone probe: agent.tools (removed spec 17, #3894 — retiredKey)", + "tombstone probe: agent.knowledge (removed spec 17, #3896 — retiredKey)", + "tombstone probe: skill.triggerPhrases (removed spec 17, #3896 — retiredKey)", + "guidance probe: skill.permissions / skill.trigger (never keys — guidance map, #5013)" + ], "steps": [ - "author one metadata item per variant (defineAgent / defineTool / defineSkill shapes from @objectstack/spec src/ai/*.zod.ts) in a writable package", - "build/boot the app; capture the boot log for AI registration lines", - "GET /api/v1/meta/types/agent (and tool, skill) to confirm the kinds are served by the registry", - "list the authored items over the REST surface (routes under /api/v1/ai/** — e.g. GET /api/v1/ai/agents, the REST-plural example of AGENTS.md PD#3)", - "re-read one item and field-diff it against the authored source" + "read the three Zod shapes FIRST and pin the requireds: AgentSchema needs name/label/role/instructions (packages/spec/src/ai/agent.zod.ts), ToolSchema needs name/label/description/parameters and is .strict() (tool.zod.ts), SkillSchema needs name/label/tools (skill.zod.ts) — if these have drifted, revise this item before running", + "author one valid item per kind in the writable package: defineAgent({ name: 'qa_probe_agent', label, role, instructions, skills: ['qa_probe_skill'] }), defineTool({ name: 'qa_probe_tool', label, description, parameters: { type: 'object', properties: {} } }), defineSkill({ name: 'qa_probe_skill', label, tools: ['qa_probe_tool'], instructions: 'probe instructions' }); build", + "author the tombstone probes from `variants` (one broken copy per retired key, e.g. tool with `permissions: []`, agent with `tools: [{...}]`, skill with `triggerPhrases: ['x']`); capture each build/parse error text verbatim", + "boot; sign in as admin; GET /api/v1/meta (meta.getTypes) and GET /api/v1/meta/types — capture which kinds are registered", + "GET /api/v1/meta/agent, /api/v1/meta/tool, /api/v1/meta/skill (route ledger: meta.getItems) and locate the three authored probes", + "GET /api/v1/meta/skill/qa_probe_skill (meta.getItem) and field-diff against the authored source, noting applied defaults (surface:'ask', active:true)", + "PUT /api/v1/meta/skill/qa_probe_skill?mode=draft with an edited copy (skill/tool are allowRuntimeCreate:true); then attempt the same runtime save for the agent kind and capture the outcome (agent is allowRuntimeCreate:false + allowOrgOverride:false — ADR-0063 §2, metadata-plugin.zod.ts)", + "repeat one meta list unauthenticated; capture status", + "POST /api/v1/ai/tools/qa_probe_tool/execute (any payload) on the open-framework boot and capture the 501 — evidence for the read-only-projection clause, NOT an expected execution" ], "acceptance": [ { - "clause": "each of the three kinds parses against its Zod schema at build — an off-spec shape fails at authoring, not silently at runtime", + "clause": "each kind's valid probe parses at build and each tombstone probe is rejected LOUDLY with the retired key NAMED and its prescription attached — never silently stripped", "oracle": "build", - "verify": "a deliberately broken variant (unknown key) is rejected with a located error; the valid ones build clean", - "evidence": "build output for both cases" + "verify": "tool.permissions error contains 'promised a capability gate on tool invocation that nothing ever enforced' (TOOL_RETIRED_KEY_GUIDANCE, tool.zod.ts); agent.tools error prescribes `skills` + ADR-0064 + 'os migrate meta --from 16' (retiredKey, agent.zod.ts); skill.triggerPhrases error says phrases were never matched and routes intent to triggerConditions (skill.zod.ts); the valid trio builds clean", + "evidence": "per-variant error texts keyed by variant + the clean build output" }, { - "clause": "the metadata registry serves all three kinds (they are registered metadata types, singular names per PD#3)", + "clause": "the metadata registry serves all three kinds under their SINGULAR names (agent, tool, skill — AGENTS.md PD#3), with schemas resolved (BUILTIN_METADATA_TYPE_SCHEMAS)", "oracle": "api", - "verify": "GET /api/v1/meta/types/ returns the kind's schema/registration for agent, tool, skill", - "evidence": "the three responses" + "verify": "GET /api/v1/meta (and /meta/types) lists agent, tool, skill; no plural variants", + "evidence": "the types responses" }, { - "clause": "authored items are listed and readable over /api/v1/ai/** and survive a field-diff against source", + "clause": "authored items list and read back field-identical over /api/v1/meta/[/], with schema defaults applied (agent.surface/skill.surface 'ask', active true)", "oracle": "api", - "verify": "list + get round-trip; every authored field present with the authored value", + "verify": "list + get round-trip for all three probes; field-diff shows every authored value plus only the documented defaults", "evidence": "list/get responses + diff" + }, + { + "clause": "the runtime write door matches the declared registry posture — BOTH sides: PUT ?mode=draft succeeds for skill (and tool), while the agent kind has NO governed runtime write path (allowRuntimeCreate:false; 'for agents, the code is the record', metadata-plugin.zod.ts — migrateStoredMetadata reports agent rows 'skipped' by design)", + "oracle": "api", + "verify": "skill draft PUT returns 200 and the draft is readable; the agent runtime save is refused/unsupported (capture the actual status), and that refusal is recorded as CORRECT, not filed as a bug", + "evidence": "the two PUT responses" + }, + { + "clause": "anonymous-deny holds on the meta surface for AI kinds", + "oracle": "api", + "verify": "unauthenticated GET /api/v1/meta/agent → 401", + "evidence": "the 401 response" + }, + { + "clause": "tool metadata is a READ-ONLY PROJECTION, not an execution entry point — authoring qa_probe_tool creates no runnable tool in the open framework (ToolSchema's own describe: no handler field; runtime executes a separately-registered AIToolDefinition, cloud-side)", + "oracle": "api", + "verify": "POST /api/v1/ai/tools/qa_probe_tool/execute answers the 501 capability-unavailable envelope (see ai.open-edition-honest-degradation); the run must NOT tick 'authored ⇒ callable'", + "evidence": "the 501 response" } ], "negative": [ - "an unauthenticated request to the /api/v1/ai/** list route → 401 (anonymous-deny holds on the AI family too)" + "any tombstone probe accepted with a clean build/parse is a FAIL — the silent strip is the exact #3896/#3820 regression these retiredKey/strict gates exist to prevent", + "an unauthenticated request to a meta list route succeeding is a FAIL (anonymous-deny must hold on the AI family too)" ], "traps": ["stale-dist"], - "source": ["packages/spec/src/ai/agent.zod.ts (+tool/skill)", "packages/runtime/src/route-ledger.ts ('* /ai/**')", "packages/spec/liveness/agent.json"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — closes the AI hole in capability coverage (no area covered agent/tool/skill kinds)", "ref": "claude/platform-test-checklist-ocwugl" }] + "source": [ + "packages/spec/src/ai/agent.zod.ts (requireds, aliases, retiredKey tools/knowledge, visibility/tenantId guidance)", + "packages/spec/src/ai/tool.zod.ts (.strict() + TOOL_RETIRED_KEY_GUIDANCE; READ-ONLY PROJECTION note)", + "packages/spec/src/ai/skill.zod.ts (requireds, retiredKey triggerPhrases, permissions/trigger guidance #5013)", + "packages/spec/src/kernel/metadata-type-schemas.ts (agent/tool/skill registered with schemas)", + "packages/spec/src/kernel/metadata-plugin.zod.ts (registry rows: agent allowRuntimeCreate:false ADR-0063 §2; tool/skill true; file patterns)", + "packages/runtime/src/route-ledger.ts (GET /meta, GET /meta/types, GET/PUT /meta/:type/:name)", + "packages/spec/liveness/agent.json + tool.json + skill.json (which props are live, and that live evidence sits in cloud)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — closes the AI hole in capability coverage (no area covered agent/tool/skill kinds)", "ref": "claude/platform-test-checklist-ocwugl" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "ai.mcp-http-surface", - "title": "MCP HTTP transport and the public /mcp/skill endpoint behave per config", + "title": "MCP HTTP transport (/mcp) and the public /mcp/skill endpoint gate exactly as documented: 404 when opted out, 501 when unimplemented, 401 anonymous, 403 scopeless-OAuth, full tool list when keyed", "since": "v15", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "api", "fixtures": { "app": "any", - "requires": ["one boot with MCP HTTP enabled and one with it disabled (the #3358 sweep hit the disabled config and read 501)"] + "requires": [ + "one boot with the MCP HTTP surface at its DEFAULT (on — served unless OS_MCP_SERVER_ENABLED=false) and one boot with OS_MCP_SERVER_ENABLED=false (the #3358 sweep hit an off config and misread the refusal)", + "an osk_ API key minted via POST /api/v1/keys (route ledger keys.create) for the authenticated half" + ] }, + "variants": [ + "OAuth scope data:read → read-family tools: list_objects, describe_object, query_records, get_record (packages/spec/src/ai/mcp.zod.ts MCP_OAUTH_SCOPE_DATA_READ)", + "OAuth scope data:write → write-family tools: create_record, update_record, delete_record", + "OAuth scope actions:execute → list_actions, run_action" + ], "steps": [ - "boot with MCP HTTP disabled; GET /mcp and /mcp/skill; record statuses", - "boot with MCP HTTP enabled; GET /mcp/skill unauthenticated", - "open an MCP Streamable HTTP session against /mcp and list tools" + "boot with OS_MCP_SERVER_ENABLED=false; GET /api/v1/mcp/skill and POST /api/v1/mcp; record status + body of both", + "boot with the default-on config; GET /api/v1/mcp/skill UNAUTHENTICATED; capture status, content-type, content-disposition and the markdown head", + "POST /api/v1/mcp/skill (wrong method); capture the 405 and its Allow header", + "POST /api/v1/mcp with NO credentials (an MCP initialize body); capture the 401 body and any WWW-Authenticate header", + "open an MCP Streamable HTTP session against /api/v1/mcp with the osk_ key: initialize, then tools/list; record the full tool-name set", + "if the OAuth track is live in the environment, request /mcp with an OAuth token carrying NONE of the three MCP scopes; capture the 403 + WWW-Authenticate error=\"insufficient_scope\"", + "capture the dispatcher-vs-hono distinction: the disabled boot's 404 comes from the gate (surface not advertised), never a hang or 500" ], "acceptance": [ { - "clause": "disabled config answers a deliberate 501/refusal on /mcp — a clean 'off', not a hang or 500", + "clause": "the opted-out boot answers 404 'MCP server is not enabled for this environment' on BOTH /mcp and /mcp/skill — a deliberate un-advertised surface, NOT the 501 (which means route mounted but MCP plugin missing: 'MCP server is not available')", "oracle": "api", - "verify": "status + body of GET /mcp on the disabled boot", - "evidence": "the response" + "verify": "status + body of both routes on the disabled boot match packages/runtime/src/domains/mcp.ts (isMcpServerEnabled → 404; unresolvable mcp service → 501); record WHICH of the two refusals was observed", + "evidence": "the two responses" + }, + { + "clause": "GET /mcp/skill serves the public SKILL.md unauthenticated on the enabled boot: 200, text/markdown; charset=utf-8, content-disposition inline; filename=\"SKILL.md\", cache-control no-store", + "oracle": "api", + "verify": "headers + markdown body head; the Connect section carries this environment's own /api/v1/mcp URL (ADR-0036 Amendment C)", + "evidence": "the response head + first lines of the markdown" }, { - "clause": "GET /mcp/skill serves the public SKILL.md for agents without auth (documented server-only surface)", + "clause": "a non-GET on /mcp/skill answers 405 with an Allow: GET header and the standard error envelope", "oracle": "api", - "verify": "200 + markdown body on the enabled boot, unauthenticated", - "evidence": "the response head" + "verify": "POST /mcp/skill → 405, Allow: GET, body message 'Method not allowed — use GET'", + "evidence": "the 405 response" }, { - "clause": "an MCP client can complete the handshake and list tools on the enabled boot", + "clause": "an anonymous /mcp request is denied 401 BEFORE any tool runs — 'Unauthorized: a valid API key is required' (or the OAuth wording plus a WWW-Authenticate resource_metadata pointer when the OAuth track is live, RFC 9728 §5.1)", + "oracle": "api", + "verify": "status/body/headers of the anonymous POST against domains/mcp.ts's two 401 forms", + "evidence": "the 401 response incl. headers" + }, + { + "clause": "a keyed MCP client completes initialize + tools/list, and the tool set is the documented principal-bound surface: list_objects, describe_object, query_records, aggregate_records, get_record, create_record, update_record, delete_record, list_actions, run_action, validate_expression", "oracle": "network", - "verify": "initialize + tools/list round-trip succeeds over Streamable HTTP", - "evidence": "the session trace" + "verify": "tools/list names against packages/mcp/src/mcp-http-tools.ts registrations (aggregate_records may be absent only when the bridge cannot route it — graceful degradation, record which)", + "evidence": "the session trace + tool-name list" + }, + { + "clause": "an OAuth token granting none of the MCP scopes is refused 403 up front with insufficient_scope naming all three scopes (data:read, data:write, actions:execute); a partial grant narrows the tool list per the variants matrix (#2698, ADR-0090 D10)", + "oracle": "api", + "verify": "403 body 'Forbidden: the access token grants none of the MCP scopes …' + WWW-Authenticate error=\"insufficient_scope\" scope=\"data:read data:write actions:execute\"; for a data:read-only token, tools/list carries the read family only", + "evidence": "the 403 + the narrowed tools/list" } ], - "negative": ["the enabled boot must still reject tool CALLS that need auth when the session carries none — transport up ≠ authz open"], + "negative": [ + "the enabled boot must still reject tool CALLS that need auth when the session carries none — transport up ≠ authz open; a tools/call succeeding anonymously is a FAIL", + "reading the 501 'MCP server is not available' as 'MCP is disabled' (or vice versa) is a recording error — the two refusals separate config-off from implementation-missing and the run record must name which one it saw" + ], "traps": ["dispatcher-vs-hono-route"], - "source": ["packages/runtime/src/route-ledger.ts ('* /mcp/**', 'GET /mcp/skill')", "#3358 §9 (MCP HTTP 501 on the swept config)"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — the #3358 sweep could not drive MCP because the config was off; both sides are now explicit", "ref": "claude/platform-test-checklist-ocwugl" }] + "automated": { "kind": "e2e", "ref": "packages/qa/dogfood/test/showcase-mcp-http-identity.dogfood.test.ts" }, + "source": [ + "packages/runtime/src/domains/mcp.ts (404/501/401/403/405 branches, exact messages)", + "packages/spec/src/ai/mcp.zod.ts (MCP_OAUTH_SCOPES + scopesToAgentPermissionSets, ADR-0090 D10)", + "packages/mcp/src/mcp-http-tools.ts (registered tool set; scope → tool-family narrowing)", + "packages/mcp/src/plugin.ts (OS_MCP_SERVER_ENABLED default-on semantics)", + "packages/runtime/src/route-ledger.ts ('* /mcp/**', 'GET /mcp/skill')", + "packages/qa/dogfood/test/showcase-mcp-self-connection.dogfood.test.ts (#3167 self-connection pin)", + "#3358 §9 (the swept config was off; both sides are now explicit)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial — the #3358 sweep could not drive MCP because the config was off; both sides are now explicit", "ref": "claude/platform-test-checklist-ocwugl" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "ai.mcp-stdio-fail-closed", - "title": "MCP stdio auto-start fails closed without an API key; with a key, reads honor RLS/FLS", + "title": "MCP stdio auto-start fails closed without OS_MCP_STDIO_API_KEY; with a member-bound key, reads AND aggregates honor RLS/FLS and revocation bites on the next call", "since": "v16", "status": "active", - "revision": 1, + "revision": 3, "priority": "P0", "surface": "cli", - "personas": ["restricted member API key (for the RLS half)"], + "personas": ["restricted member API key (for the RLS half)", "admin API key (for the full-row comparison)"], + "fixtures": { + "app": "any", + "requires": [ + "an OWD-private object with rows owned by two different users (the showcase task/invoice seeds qualify when run against the showcase)", + "two osk_ API keys: one bound to a restricted member, one to an admin (Setup → Connect an Agent, or POST /api/v1/keys)", + "for the aggregate half: a field on that object the restricted member cannot read (the same FLS-masked field the row/field clause relies on), plus at least one readable field to group a count by" + ], + "knownGaps": [ + "aggregate_records is registered on the MCP transport only when the runtime routes aggregation through the ObjectQL engine (mcp-http-tools.ts registers it IFF the bridge exposes aggregate — graceful degradation, the same contract as the action bridge). The showcase runtime does route it through the engine (packages/runtime/src/domains/mcp.ts). If tools/list lacks aggregate_records on this boot, record the aggregate clauses blocked(dependency), not a fail" + ] + }, "steps": [ - "attempt MCP stdio auto-start WITHOUT OS_MCP_STDIO_API_KEY set; capture the refusal", - "start it with a valid key bound to a restricted member; over MCP, read an OWD-private object", - "read the same object as an admin-keyed session; compare row sets" + "start the app with the stdio transport enabled (OS_MCP_STDIO_ENABLED=true, or the plugin autoStart option) and OS_MCP_STDIO_API_KEY UNSET; capture the startup failure", + "start again with OS_MCP_STDIO_API_KEY=osk_unknown (a key that resolves to nothing); capture that failure", + "start with the restricted member's valid key; capture the principal-bound startup log line", + "over MCP stdio, query_records the OWD-private object and get_record a row owned by the OTHER user; record row sets and field sets", + "run the same two reads over REST as the same member (Bearer the same key) and as the admin key; compare", + "aggregate parity: over MCP stdio as the member, aggregate_records the OWD-private object with a count grouped by a readable field ({ aggregations: [{ function: 'count', alias: 'n' }], groupBy: [''] }); run the SAME aggregate over REST as the member and as the admin; compare the per-group counts", + "FLS aggregate fail-closed: over MCP stdio as the member, aggregate_records with an aggregation (or a groupBy) naming the field the member cannot read (the masked field above); capture the refusal and confirm no numeric result is returned", + "revoke the member key while the stdio session is up; issue one more read; capture the refusal", + "start once more with stdio NOT enabled and no key set; confirm a clean boot (the key is only demanded when stdio is on)" ], "acceptance": [ { - "clause": "stdio auto-start without the key refuses to serve (fail-closed), with a clear message naming the missing key", + "clause": "enabled-but-keyless auto-start REFUSES to serve, naming the missing var and the remedy: '[MCP] The stdio transport is enabled (OS_MCP_STDIO_ENABLED / autoStart) but OS_MCP_STDIO_API_KEY is not set. … Refusing to start an unscoped stdio server (ADR-0101).'", + "oracle": "log", + "verify": "plugin.start throws /OS_MCP_STDIO_API_KEY/ (packages/mcp/src/plugin.ts); the process does not serve", + "evidence": "the thrown message / startup log" + }, + { + "clause": "an unknown/revoked/expired key is rejected up front — 'OS_MCP_STDIO_API_KEY did not resolve to a valid identity (unknown / revoked / expired / owner-less). Refusing to start stdio (ADR-0101).' — never a fall-back to an anonymous-but-serving session", + "oracle": "log", + "verify": "startup with osk_unknown throws /did not resolve to a valid identity/", + "evidence": "the thrown message" + }, + { + "clause": "a valid key binds the transport to a REAL principal and says so: '[MCP] stdio transport principal-bound to OS_MCP_STDIO_API_KEY identity (RLS/FLS/tenant applied)'", "oracle": "log", - "verify": "the refusal line in the process output", + "verify": "the startup line carries the member's userId", "evidence": "the log line" }, { - "clause": "with a member-bound key, MCP reads return only RLS-visible rows and FLS-masked fields — identical to the same member over REST", + "clause": "member-keyed MCP reads return exactly the member's REST row/field sets (RLS rows hidden, FLS fields masked), while the admin key sees the full set — both sides of the gate", + "oracle": "api", + "verify": "row-set + field-set comparison MCP-vs-REST per principal; the OTHER user's row absent for the member on both surfaces, present for admin", + "evidence": "the four compared reads" + }, + { + "clause": "member-keyed MCP aggregate_records equals THAT member's REST aggregate, NOT the admin's — the group counts reconcile to the member's own RLS-scoped rows (the other user's rows are excluded from the member's counts on both surfaces; the admin's counts are strictly higher wherever the member's RLS hides rows). MCP aggregation routes through the ObjectQL engine read path so RLS always runs; the raw per-env driver is deliberately not passed (§G1 #2976)", + "oracle": "api", + "verify": "per-group count comparison MCP-vs-REST for the member matches; member counts < admin counts on the buckets the member cannot fully see (packages/runtime/src/domains/mcp.ts aggregate → callData('aggregate') resolves the engine so the security middleware runs)", + "evidence": "the member MCP aggregate, the member REST aggregate, and the admin REST aggregate" + }, + { + "clause": "an aggregate whose input or groupBy names an FLS-unreadable field fails CLOSED — the FLS aggregate-INPUT gate rejects before any statistic is computed ('Field read denied', details.forbiddenFields: []); a masked field's sum/count_distinct never leaks through an alias, because result masking cannot run on aggregate output rows (they carry only aliases) — so the leak is stopped on the input (#2976)", "oracle": "api", - "verify": "row-set + field-set comparison MCP-vs-REST for the same member; admin session shows the full set (both sides)", - "evidence": "the compared reads" + "verify": "aggregate_records over the masked field errors with the field-read-denied refusal and returns no numeric result; the same aggregate over a READABLE field succeeds (and count(*) needs no field) — the gate targets the field, not the operation", + "evidence": "the refusal + a control aggregate over a readable field" + }, + { + "clause": "revocation is honored on the NEXT read (the identity is re-resolved per call), failing with 'MCP stdio identity is no longer valid (key revoked or expired)'", + "oracle": "log", + "verify": "the post-revocation read errors with that message instead of serving stale authority", + "evidence": "the refusal" + }, + { + "clause": "stdio disabled ⇒ no key demanded: the boot completes and logs '[MCP] Transport not auto-started …' (the HTTP surface is served per-request regardless)", + "oracle": "log", + "verify": "clean start with neither OS_MCP_STDIO_ENABLED nor a key", + "evidence": "the info line" } ], - "negative": ["an invalid/revoked key must be rejected, not fall back to an anonymous-but-serving session"], + "negative": [ + "an invalid/revoked key falling back to an anonymous-but-serving session is THE fail this item exists for — any served read without a resolved principal is a P0 FAIL", + "a member-keyed MCP read returning rows the same member's REST read hides is a FAIL (MCP must not be a side door around RLS)", + "an MCP aggregate count that equals the ADMIN's total (i.e. counts rows the same member's REST read hides) is a P0 FAIL — an RLS-bypassing count is the sys_attachment-total-leak class (#2976): a bypass leaks row existence and statistics even when the individual rows stay hidden", + "an aggregate over an FLS-masked field returning a number instead of failing closed is a FAIL — the input gate is the only place the leak is stopped, since output masking cannot see through an alias" + ], "traps": ["wrong-persona"], - "source": ["#3358 §9 (verified PASS: fail-closed guard present, reads honor RLS/FLS)"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358 §9", "ref": "#3358" }] + "automated": { "kind": "unit", "ref": "packages/mcp/src/__tests__/plugin.test.ts" }, + "source": [ + "packages/mcp/src/plugin.ts (the three refusal strings + per-call re-resolution, ADR-0101)", + "packages/mcp/src/__tests__/plugin.test.ts ('stdio principal admission — fail-closed')", + "packages/runtime/src/domains/mcp.ts (the aggregate bridge routes through callData('aggregate') → the ObjectQL engine so RLS + the FLS aggregate gate always run; the raw driver is deliberately NOT passed)", + "packages/mcp/src/mcp-http-tools.ts (aggregate_records registration: 'Runs under the caller's permissions, row-level security and field-level security'; registered only when the bridge exposes aggregate)", + "packages/plugins/plugin-security/src/security-plugin.test.ts (FLS aggregate-INPUT gate: aggregating OR grouping-by an unreadable field is denied fail-closed with details.forbiddenFields; readable fields aggregate fine)", + "#3358 §9 (verified PASS: fail-closed guard present, reads honor RLS/FLS)", + "docs/plans/release-15.1-test-plan.md §G1 (#2976 — aggregate_records走 ENGINE 读路径; RLS/tenant 与 find 一致; FLS 输入门 fail-closed)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358 §9", "ref": "#3358" }, + { "revision": 2, "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-08-08", "change": "clause-extension (§G1 #2976): member-keyed aggregate_records reconciles to the member's OWN REST aggregate (RLS parity, not the admin's), and an aggregate over an FLS-masked field fails closed on the input gate — the RLS-bypassing count is the sys_attachment-total-leak class", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "ai.mcp-run-action-exposure-gate", - "title": "MCP run_action requires ai.exposed and audits the call", + "title": "MCP run_action requires ai.exposed (fail-closed with the prescription), filters list_actions to exposed+permitted, refuses system objects, and logs the trusted-elevation audit line", "since": "v15.1", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "api", - "fixtures": { "app": "any", "requires": ["one action declared ai.exposed and one without, on the same object"] }, + "fixtures": { + "app": "showcase", + "requires": [ + "the showcase's ONE ai-exposed action: showcase_portfolio_snapshot (global script action, ai: { exposed: true }, examples/app-showcase/src/ui/actions/index.ts) — the exposed side", + "any showcase action WITHOUT an ai block (e.g. showcase_mark_done on showcase_task) — the undeclared side", + "MCP reachable with an osk_ key (see ai.mcp-http-surface)" + ] + }, "steps": [ - "over MCP, call run_action on the undeclared action; capture the refusal", - "call the ai.exposed action; capture result", - "read the audit log for the successful call" + "over MCP, call list_actions (no objectName, then objectName: 'showcase_task'); record which actions appear", + "call run_action on the UNDECLARED action (actionName: 'showcase_mark_done', a real recordId); capture the refusal", + "verify the refusal left zero side effects: read the task over REST — status unchanged", + "call run_action on the exposed action (actionName: 'showcase_portfolio_snapshot'); capture the result payload ({ ok, scope: 'global', accounts, projects, invoices })", + "capture the server log for the successful call", + "call run_action against an action on a system object (any sys_* action name) and capture that refusal", + "cross-check the exposed action's counts against direct REST counts of showcase_account / showcase_project / showcase_invoice" ], "acceptance": [ { - "clause": "the undeclared action is rejected fail-closed (declared ≠ exposed is the gate, PD#10 discipline)", + "clause": "the undeclared action is rejected fail-closed with the exact prescription: \"Action 'showcase_mark_done' on 'showcase_task' is not exposed to AI — the app author must opt it in with `ai: { exposed: true, description: … }`\" (declared ≠ exposed is the gate, PD#10 discipline)", "oracle": "api", - "verify": "the MCP call errors with the exposure refusal; the action did NOT execute (verify by its side effect being absent)", - "evidence": "the refusal + absence check" + "verify": "the MCP error text matches actionAiExposureError (packages/runtime/src/action-execution.ts); the REST re-read shows the record untouched", + "evidence": "the refusal + the unchanged-record read" }, { - "clause": "the exposed action executes and lands in the audit log with the caller identity", + "clause": "list_actions returns ONLY actions that are both ai-exposed and permitted to the caller — showcase_portfolio_snapshot present, showcase_mark_done absent", "oracle": "api", - "verify": "action side effect present + audit row for the call", - "evidence": "result + audit row" + "verify": "the list_actions payload against the fixture's declared ai blocks (mcp-http-tools.ts: 'actions that are BOTH declared ai-exposed and that the caller is permitted to run')", + "evidence": "the list_actions response" + }, + { + "clause": "the exposed action executes and returns its declared result, whose counts reconcile with direct REST counts", + "oracle": "api", + "verify": "run_action result {accounts, projects, invoices} equals the three REST count queries", + "evidence": "result + the three counts" + }, + { + "clause": "the trusted-elevation is AUDIBLE: the server log carries \"[action-audit] MCP run_action 'showcase_portfolio_snapshot' on …— body executes TRUSTED (system-elevated context, RLS/FLS-bypassing) for user ''\" naming the caller (and the on-behalf-of user for agent principals)", + "oracle": "log", + "verify": "the audit line for the successful call (action-execution.ts #2849/#3914)", + "evidence": "the log excerpt" + }, + { + "clause": "system objects are refused wholesale: run_action against a sys_* object errors with the system-object guard ('… is on a system object and is not exposed via MCP'), never executes", + "oracle": "api", + "verify": "the refusal text + absence of any side effect", + "evidence": "the refusal" } ], - "negative": ["the refusal path must leave zero side effects — a rejected call that half-executed is a FAIL"], - "source": ["docs/plans/release-15.1-test-plan.md §A9 (#2964)"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the 15.1 plan §A9", "ref": "claude/platform-test-checklist-ocwugl" }] + "negative": [ + "the refusal path must leave zero side effects — a rejected call that half-executed is a FAIL", + "list_actions advertising an action that run_action then refuses (or vice versa) is a FAIL — the two doors enforce the SAME ai.exposed declaration (action-execution.ts: 'the REST /actions route and the MCP run_action bridge enforce the SAME declaration')" + ], + "traps": ["wrong-persona"], + "source": [ + "packages/runtime/src/action-execution.ts (actionAiExposureError exact string; system-object guard; [action-audit] line; shared REST/MCP gate #3915)", + "packages/mcp/src/mcp-http-tools.ts (list_actions/run_action registration + exposed-and-permitted filter)", + "examples/app-showcase/src/ui/actions/index.ts (showcase_portfolio_snapshot — the seeded ai.exposed fixture)", + "docs/plans/release-15.1-test-plan.md §A9 (#2964) + §G2 (#3010/#3020 standalone action on the bridge)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from the 15.1 plan §A9", "ref": "claude/platform-test-checklist-ocwugl" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] }, { "id": "ai.mcp-validate-expression", - "title": "MCP validate_expression returns errors/warnings/inferred type against an object schema", + "title": "MCP validate_expression returns build-accurate errors/warnings/inferred type + in-scope context for every site variant, and fail-closes on unknown/system objects", + "since": "v16", + "status": "active", + "revision": 2, + "priority": "P2", + "surface": "api", + "fixtures": { + "app": "showcase", + "requires": [ + "MCP HTTP reachable with an osk_ key (see ai.mcp-http-surface)", + "a seeded object with number + text + date fields to probe against (showcase_task: est_hours/title/created_at qualify)" + ] + }, + "variants": [ + "site: formula (record. bound; inferred type returned)", + "site: validation (record-scoped predicate)", + "site: flow_condition (fields bound BARE — a record.-prefixed ref is the error here)", + "site: template" + ], + "steps": [ + "over MCP, call validate_expression { objectName: 'showcase_task', expression: 'record.est_hours * 1.1', site: 'formula' } — a sound formula", + "call it with a BARE field ref in formula site ('est_hours * 1.1') and with an unknown field ('record.est_hourz * 1'); capture both error sets", + "call it with a text field misused in arithmetic ('record.title * 2'); capture the WARNING (not error)", + "call the flow_condition site with bare fields ('est_hours > 8') — must validate clean", + "call it against a nonexistent object and against a sys_* object; capture both refusals", + "record the inScope payload (dialect/roots/fields/functions) returned alongside each verdict" + ], + "acceptance": [ + { + "clause": "the sound formula returns ok:true with an inferredType and the in-scope fields/functions — the same verdict `objectstack build` would give (the tool exists so agents self-correct BEFORE authoring, #1928)", + "oracle": "api", + "verify": "response has ok:true, inferredType present, inScope.fields includes est_hours", + "evidence": "the response" + }, + { + "clause": "bare-field-in-formula and unknown-field are ERRORS with located, prescriptive text (unknown field carries a did-you-mean where close); text-in-arithmetic is a WARNING, not an error — the error/warning tiers must not blur", + "oracle": "api", + "verify": "three calls, three verdicts matching the tiers pinned in packages/mcp/src/mcp-validate-expression.test.ts", + "evidence": "the three responses" + }, + { + "clause": "site changes the binding rules: bare fields are CORRECT in flow_condition and wrong in formula — the same expression flips verdict across the two sites", + "oracle": "api", + "verify": "'est_hours > 8' ok under flow_condition, error under formula", + "evidence": "the paired responses" + }, + { + "clause": "unknown objects error clearly ('Object \"\" not found') and system objects are refused by the fail-closed guard — the validator is schema introspection, never a data door", + "oracle": "api", + "verify": "both refusal texts; no schema of a sys_* object is leaked in the response", + "evidence": "the two refusals" + } + ], + "negative": [ + "a validation verdict that DIFFERS from the build gate for the same expression is a FAIL — the tool's one job is build-accuracy (api-backend.formula-gates is the same engine)" + ], + "automated": { "kind": "unit", "ref": "packages/mcp/src/mcp-validate-expression.test.ts" }, + "source": [ + "packages/mcp/src/mcp-http-tools.ts (validate_expression registration: input schema incl. the 4-value site enum, VALIDATE_SITE_MAP, response shape)", + "packages/mcp/src/mcp-validate-expression.test.ts (tier pins: error vs warning vs ok, unknown object, system-object guard)", + "#3358 §9 (underlying validateExpression works; MCP path was blocked on the disabled transport)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358 §9", "ref": "#3358" }, + { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "ai.skill-instructions-mcp-prompts", + "title": "Authored skill instructions project onto the MCP prompts primitive (#3905) — listed with identity, fetchable by name, and NOT projected when instruction-less or inactive", + "since": "v17", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "fixtures": { + "app": "any", + "requires": [ + "three authored skills in a writable package: qa_prompt_full (instructions + label + description), qa_prompt_bare (tools only, NO instructions), qa_prompt_off (instructions but active:false)", + "MCP HTTP reachable with an osk_ key (see ai.mcp-http-surface)" + ], + "knownGaps": [ + "the OTHER half of a skill — tools[] / surface / triggerConditions binding — is CLOUD-RUNTIME-ONLY (#3905; skill.zod.ts says so per-key): the open framework has no agent loop to bind to, so this item deliberately does NOT test tool binding or activation; those need a cloud/EE environment" + ] + }, + "steps": [ + "author the three fixture skills; build; boot with MCP enabled", + "open an MCP session (osk_ key) and check the server's declared capabilities for prompts", + "prompts/list; record the projected names", + "prompts/get qa_prompt_full; capture the returned message body", + "prompts/get qa_prompt_bare (not projected) and prompts/get a wholly unknown name; capture both errors", + "flip qa_prompt_off to active:true, reload metadata, and re-list" + ], + "acceptance": [ + { + "clause": "qa_prompt_full is listed as an MCP prompt carrying the skill's name, label (title) and description — the metadata → prompts projection is live in the OPEN framework, not just cloud", + "oracle": "network", + "verify": "prompts/list contains qa_prompt_full with the authored identity (packages/mcp/src/skill-prompts.ts projectSkillPrompt)", + "evidence": "the prompts/list payload" + }, + { + "clause": "prompts/get returns the authored instructions text as the prompt body, re-read from metadata at get time", + "oracle": "network", + "verify": "the message content equals the authored instructions string", + "evidence": "the prompts/get payload" + }, + { + "clause": "a skill with NO instructions is not listed at all (nothing to serve ⇒ not advertised), and an inactive skill is not projected — both absences verified, not assumed", + "oracle": "network", + "verify": "prompts/list omits qa_prompt_bare and qa_prompt_off; after activating qa_prompt_off it appears", + "evidence": "the before/after listings" + }, + { + "clause": "prompts/get for a non-projected or unknown name is rejected with JSON-RPC invalid-params (-32602), not an empty success", + "oracle": "network", + "verify": "both bad gets error with code -32602 (skill-prompts.test.ts pin)", + "evidence": "the two error responses" + }, + { + "clause": "when the host cannot read skill metadata the prompts capability is NOT declared — never advertised-but-empty (graceful degradation, same posture as the tool bridges)", + "oracle": "test", + "verify": "run packages/mcp/src/skill-prompts.test.ts ('declares NO prompts capability when the host cannot read skill metadata') and cite its output — do not hand-build a broken host", + "evidence": "the test output" + } + ], + "negative": [ + "an instruction-less skill appearing in prompts/list is a FAIL — an empty prompt advertisement is the 'declared capability nothing serves' shape this projection was built to avoid", + "prompts/get succeeding for an inactive skill is a FAIL (active:false must withdraw the projection, unlike the retired tool.active which withdrew nothing)" + ], + "traps": ["stale-dist"], + "automated": { "kind": "unit", "ref": "packages/mcp/src/skill-prompts.test.ts" }, + "source": [ + "packages/mcp/src/skill-prompts.ts (#3905 — the projection, its narrowness, and the two-halves boundary)", + "packages/mcp/src/skill-prompts.test.ts (list/get/absence/-32602/capability pins)", + "packages/spec/src/ai/skill.zod.ts (instructions served everywhere; tools/surface/triggerConditions cloud-only)", + "packages/spec/liveness/skill.json (instructions/name/label/description/active live in-repo via skill-prompts.ts since 2026-08-06)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: the open framework's one live skill consumer (#3905) had no checklist coverage", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "ai.open-edition-honest-degradation", + "title": "Open-framework /ai/** degrades honestly: GET /ai/agents answers the empty-catalog courtesy, every other /ai/* answers 501 with the Cloud/EE remedy sentence that discovery also reports", "since": "v16", "status": "active", "revision": 1, "priority": "P2", "surface": "api", - "fixtures": { "app": "any", "requires": ["MCP HTTP enabled (see ai.mcp-http-surface)"] }, + "personas": ["admin"], + "fixtures": { + "app": "any", + "requires": ["a stock OPEN-framework boot — no @objectstack/service-ai registered (the default: service-ai is a private Cloud/EE package)"], + "knownGaps": [ + "ModelRegistrySchema (packages/spec/src/ai/model-registry.zod.ts) and ConversationSessionSchema (conversation.zod.ts) have NO open-framework runtime consumer — /ai/models and /ai/conversations/* are served only by cloud service-ai (enumerated in cloud's ai-route-ledger.ts, #3718). This item therefore tests the HONEST REFUSAL, not those payload shapes; asserting model/conversation behavior needs a cloud/EE environment and belongs to that repo's conformance test", + "the ai settings manifest (packages/services/service-settings/src/manifests/ai.manifest.ts) configures the CLOUD adapter selection; it stores values in the open framework but nothing consumes them here — do not tick 'AI configured' off a saved settings form" + ] + }, "steps": [ - "over MCP, validate a correct formula against a seeded object schema", - "validate a formula with a type error (e.g. date arithmetic) and one referencing a nonexistent field" + "boot the stock open framework; sign in as admin", + "GET /api/v1/ai/agents; capture status + body shape", + "GET /api/v1/ai/models and GET /api/v1/ai/conversations; capture both", + "POST /api/v1/ai/chat with a minimal body; capture", + "GET /api/v1/discovery and extract the ai service slot's availability + message", + "diff the 501 body message against the discovery message" ], "acceptance": [ { - "clause": "the valid expression returns ok with an inferred type; the invalid ones return located errors naming the problem (bad arithmetic, unknown field)", + "clause": "GET /ai/agents answers 200 with the declared envelope carrying { agents: [] } — the deliberate courtesy (#4058) that keeps the console's per-navigation poll from logging spam, and it must be the RELOCATED payload under data (data.agents), not a bare array", "oracle": "api", - "verify": "three calls, three verdicts matching the formula gates the build enforces (api-backend.formula-gates)", + "verify": "status 200; body data.agents is an empty ARRAY (unwrapResponse → .agents readable)", + "evidence": "the response" + }, + { + "clause": "every other /ai/* route answers 501 (route mounted, implementation absent — NOT 404, NOT 503) with the exact remedy: 'Provided by @objectstack/service-ai in ObjectStack Cloud/Enterprise — no implementation ships in the open framework'", + "oracle": "api", + "verify": "/ai/models, /ai/conversations, /ai/chat all 501 with that message (REMEDY_DETAIL, packages/spec/src/system/core-services.zod.ts)", "evidence": "the three responses" + }, + { + "clause": "the 501 body and the discovery entry for the ai slot carry the SAME sentence — the one-source rule that stops the two surfaces prescribing different remedies", + "oracle": "api", + "verify": "discovery's ai slot message string-equals the 501 message (serviceUnavailableMessage is the single writer)", + "evidence": "the diff" + }, + { + "clause": "anonymous-deny still precedes the degradation: an unauthenticated /ai/* request is answered by the auth gate, not by the capability answer", + "oracle": "api", + "verify": "unauthenticated GET /ai/models → 401, not 501", + "evidence": "the 401" } ], - "source": ["#3358 §9 (underlying validateExpression works; MCP path was blocked on the disabled transport)"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from #3358 §9", "ref": "#3358" }] + "negative": [ + "a 404 ROUTE_NOT_FOUND with the 'check discovery' hint on a mounted /ai/* route is the exact pre-#4058/#3842 failure this item pins — FAIL", + "ticking any AI capability as PRESENT from the 200 empty-agents courtesy (or from a saved ai settings form) is a recording error — the empty catalog IS the 'no AI here' signal" + ], + "traps": ["dispatcher-vs-hono-route"], + "source": [ + "packages/runtime/src/domains/ai.ts (the /ai/agents empty-list courtesy #4058/#4053 + the shared 501 exit)", + "packages/runtime/src/domains/unavailable.ts (501-vs-404-vs-503 rationale; message single-sourced from spec)", + "packages/spec/src/system/core-services.zod.ts (REMEDY_DETAIL['ai'] exact sentence; ai slot 'optional')", + "packages/runtime/src/route-ledger.ts ('* /ai/**' dynamic row — routes owned by cloud; this repo cannot enumerate them)", + "packages/spec/src/ai/model-registry.zod.ts + conversation.zod.ts (schemas exist; no in-repo runtime consumer — the knownGaps basis)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: pins the open/cloud AI boundary as testable behavior (courtesy list + 501 remedy + discovery parity) instead of leaving /ai/** unswept; model-registry & conversation runtime behavior recorded as knownGaps rather than invented", "ref": "claude/platform-test-checklist-ocwugl" } + ] } ] } diff --git a/docs/qa/platform-checklist/areas/api-backend.json b/docs/qa/platform-checklist/areas/api-backend.json index efded2dfd9..0c00890dbc 100644 --- a/docs/qa/platform-checklist/areas/api-backend.json +++ b/docs/qa/platform-checklist/areas/api-backend.json @@ -11,10 +11,15 @@ "revision": 2, "priority": "P1", "surface": "api", - "personas": ["admin (or any member entitled to the objects the batch touches)"], + "personas": [ + "admin (or any member entitled to the objects the batch touches)" + ], "fixtures": { "app": "showcase", - "requires": ["two batch-writable objects (showcase_private_note and showcase_task both accept baseline member creates)", "default maxBatchSize (200 unless RestServerConfig.batch overrides)"] + "requires": [ + "two batch-writable objects (showcase_private_note and showcase_task both accept baseline member creates)", + "default maxBatchSize (200 unless RestServerConfig.batch overrides)" + ] }, "steps": [ "boot showcase isolated; GET /api/v1/discovery and read capabilities.transactionalBatch (also compare client.capabilities.transactionalBatch if driving via @objectstack/client)", @@ -56,12 +61,32 @@ "evidence": "response + re-reads" } ], - "negative": ["the two batch doors must not be conflated: atomic:false must NOT be accepted-and-ignored on /api/v1/batch (silent non-atomic acceptance is the pre-ADR-0119 bug shape), and the per-object door must NOT silently roll back valid rows when a sibling row fails without atomic being requested"], - "traps": ["dispatcher-vs-hono-route"], - "source": ["#3358 §9", "packages/spec/src/api/batch.zod.ts (ADR-0119 D4)", "packages/spec/src/api/error-code-ledger.zod.ts (BATCH_NOT_ATOMIC, BATCH_TOO_LARGE, NOT_ATTEMPTED, ROLLED_BACK — #4793)", "packages/rest/src/rest-route-ledger.ts (batch family)", "packages/spec/src/api/discovery.zod.ts (transactionalBatch)"], + "negative": [ + "the two batch doors must not be conflated: atomic:false must NOT be accepted-and-ignored on /api/v1/batch (silent non-atomic acceptance is the pre-ADR-0119 bug shape), and the per-object door must NOT silently roll back valid rows when a sibling row fails without atomic being requested" + ], + "traps": [ + "dispatcher-vs-hono-route" + ], + "source": [ + "#3358 §9", + "packages/spec/src/api/batch.zod.ts (ADR-0119 D4)", + "packages/spec/src/api/error-code-ledger.zod.ts (BATCH_NOT_ATOMIC, BATCH_TOO_LARGE, NOT_ATTEMPTED, ROLLED_BACK — #4793)", + "packages/rest/src/rest-route-ledger.ts (batch family)", + "packages/spec/src/api/discovery.zod.ts (transactionalBatch)" + ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from #3358", + "ref": "#3358" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -105,12 +130,28 @@ "evidence": "build output" } ], - "negative": ["a green harness run that silently loaded src instead of dist is a false pass — capture the resolved module path as part of the evidence; if the path points into src/, the clause is not-run, not pass"], - "traps": ["stale-dist"], - "source": ["#3358 §9"], + "negative": [ + "a green harness run that silently loaded src instead of dist is a false pass — capture the resolved module path as part of the evidence; if the path points into src/, the clause is not-run, not pass" + ], + "traps": [ + "stale-dist" + ], + "source": [ + "#3358 §9" + ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from #3358", + "ref": "#3358" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -154,11 +195,31 @@ "evidence": "run record" } ], - "negative": ["also sample one key documented as silently-stripped-by-design (e.g. pure display annotations) to confirm the strip stays deliberate and documented, not accidental — and a tombstoned key that parses CLEAN anywhere is a P1 FAIL (the silent fourth state ADR-0049/0078 forbids)"], - "source": ["#3358 §9", "ADR-0049", "ADR-0087", "#4286 (query tombstones)", "ADR-0114 D4 / #3977 (fieldErrors)", "packages/spec/src/shared/retired-key.ts", "packages/spec/src/data/filter.zod.ts (RetiredFilterOperatorGuidance)"], + "negative": [ + "also sample one key documented as silently-stripped-by-design (e.g. pure display annotations) to confirm the strip stays deliberate and documented, not accidental — and a tombstoned key that parses CLEAN anywhere is a P1 FAIL (the silent fourth state ADR-0049/0078 forbids)" + ], + "source": [ + "#3358 §9", + "ADR-0049", + "ADR-0087", + "#4286 (query tombstones)", + "ADR-0114 D4 / #3977 (fieldErrors)", + "packages/spec/src/shared/retired-key.ts", + "packages/spec/src/data/filter.zod.ts (RetiredFilterOperatorGuidance)" + ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from #3358", + "ref": "#3358" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -169,7 +230,10 @@ "revision": 2, "priority": "P2", "surface": "api", - "personas": ["admin", "non-admin member"], + "personas": [ + "admin", + "non-admin member" + ], "steps": [ "start the REAL dev server (os dev) with OS_SERVER_TIMING enabled — never a simulated dispatch; the #3361 regression lived exactly in the dispatcher-vs-hono seam while unit tests stayed green", "as admin: GET /api/v1/data/showcase_task with the X-OS-Debug-Timing request header; dump all response headers", @@ -203,12 +267,30 @@ "evidence": "the raw traces incl. host:port" } ], - "negative": ["a run that only checks the admin side is at best partial: the non-admin absence is the security half of the item, and it must be measured on the same live server in the same run"], - "traps": ["dispatcher-vs-hono-route", "wrong-persona"], - "source": ["#3358 §9", "#3361"], + "negative": [ + "a run that only checks the admin side is at best partial: the non-admin absence is the security half of the item, and it must be measured on the same live server in the same run" + ], + "traps": [ + "dispatcher-vs-hono-route", + "wrong-persona" + ], + "source": [ + "#3358 §9", + "#3361" + ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; oracle pinned to the live server because of #3361", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from #3358; oracle pinned to the live server because of #3361", + "ref": "#3358" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -219,10 +301,16 @@ "revision": 1, "priority": "P0", "surface": "api", - "personas": ["admin (baseline known-answer runs)", "contributor (the RLS/FLS-restricted persona for the expand enforcement clause)"], + "personas": [ + "admin (baseline known-answer runs)", + "contributor (the RLS/FLS-restricted persona for the expand enforcement clause)" + ], "fixtures": { "app": "showcase", - "requires": ["seeded showcase_account rows (Northwind, Contoso, Fabrikam, Stark Industries, 华宁科技, … with status ∈ {active, prospect, churned}, sales_region ∈ {amer, emea, apac}, numeric annual_revenue, date signed_on, churn_reason null except churned rows) — the known-answer dataset", "seeded showcase_task rows (status ∈ {backlog, todo, in_progress, in_review, done}, assignee emails, numeric progress/estimate_hours, project lookup)"] + "requires": [ + "seeded showcase_account rows (Northwind, Contoso, Fabrikam, Stark Industries, 华宁科技, … with status ∈ {active, prospect, churned}, sales_region ∈ {amer, emea, apac}, numeric annual_revenue, date signed_on, churn_reason null except churned rows) — the known-answer dataset", + "seeded showcase_task rows (status ∈ {backlog, todo, in_progress, in_review, done}, assignee emails, numeric progress/estimate_hours, project lookup)" + ] }, "steps": [ "boot showcase isolated; as admin GET /api/v1/data/showcase_account with no filter and a generous $top — this unfiltered baseline is the local ground truth every operator answer is computed from (never hardcode counts; the seed can drift)", @@ -291,13 +379,59 @@ "evidence": "run record" } ], - "negative": ["silent WIDENING is the catastrophic failure mode (#3899): a malformed query body (e.g. {\"filter\": {…}} — not a QueryAST key) must answer 400 VALIDATION_FAILED, never degrade into an unfiltered 200 full read; verify by asserting the malformed-body probe's status AND that its response row count is not the full table"], - "variants": ["op:eq", "op:ne", "op:gt", "op:gte", "op:lt", "op:lte", "op:in", "op:not_in", "op:between", "op:contains", "op:not_contains", "op:starts_with", "op:ends_with", "op:like (driver-verbatim pattern, NOT auto-%-wrapped — canonicalAstOperator keeps it distinct)", "op:is_null", "op:is_not_null", "param:$top", "param:$skip", "param:$select", "param:$orderby", "param:$count", "param:$search", "param:$filter", "param:$expand"], - "automated": { "kind": "unit", "ref": "packages/objectql/src/engine.test.ts ([#2850] expand sub-read through the secured find path) + packages/objectql/src/filter-comparand-shape.ts (#5869 gate)" }, - "traps": ["seed-data-thin", "single-datapoint", "dispatcher-vs-hono-route"], - "source": ["packages/spec/src/data/filter.zod.ts (AST_OPERATOR_MAP / FieldOperatorsSchema — the operator variant source)", "packages/spec/src/data/query.zod.ts (QueryAST keys)", "packages/metadata-protocol/src/protocol.ts (supported $-params + UNSUPPORTED_QUERY_PARAM #2926 ⑩, INVALID_FIELD #4134)", "#5869 (commit 10c4ea9)", "release-15.1 plan §A6", "examples/app-showcase/src/data/seed/index.ts"], + "negative": [ + "silent WIDENING is the catastrophic failure mode (#3899): a malformed query body (e.g. {\"filter\": {…}} — not a QueryAST key) must answer 400 VALIDATION_FAILED, never degrade into an unfiltered 200 full read; verify by asserting the malformed-body probe's status AND that its response row count is not the full table" + ], + "variants": [ + "op:eq", + "op:ne", + "op:gt", + "op:gte", + "op:lt", + "op:lte", + "op:in", + "op:not_in", + "op:between", + "op:contains", + "op:not_contains", + "op:starts_with", + "op:ends_with", + "op:like (driver-verbatim pattern, NOT auto-%-wrapped — canonicalAstOperator keeps it distinct)", + "op:is_null", + "op:is_not_null", + "param:$top", + "param:$skip", + "param:$select", + "param:$orderby", + "param:$count", + "param:$search", + "param:$filter", + "param:$expand" + ], + "automated": { + "kind": "unit", + "ref": "packages/objectql/src/engine.test.ts ([#2850] expand sub-read through the secured find path) + packages/objectql/src/filter-comparand-shape.ts (#5869 gate)" + }, + "traps": [ + "seed-data-thin", + "single-datapoint", + "dispatcher-vs-hono-route" + ], + "source": [ + "packages/spec/src/data/filter.zod.ts (AST_OPERATOR_MAP / FieldOperatorsSchema — the operator variant source)", + "packages/spec/src/data/query.zod.ts (QueryAST keys)", + "packages/metadata-protocol/src/protocol.ts (supported $-params + UNSUPPORTED_QUERY_PARAM #2926 ⑩, INVALID_FIELD #4134)", + "#5869 (commit 10c4ea9)", + "release-15.1 plan §A6", + "examples/app-showcase/src/data/seed/index.ts" + ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "new — query-contract matrix over the spec operator vocabulary with known-answer checks, per the deep-test contract", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "new — query-contract matrix over the spec operator vocabulary with known-answer checks, per the deep-test contract", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -308,10 +442,17 @@ "revision": 1, "priority": "P1", "surface": "api", - "personas": ["admin", "a baseline member (for the 403 sample)", "anonymous (for the 401 sample)"], + "personas": [ + "admin", + "a baseline member (for the 403 sample)", + "anonymous (for the 401 sample)" + ], "fixtures": { "app": "showcase", - "requires": ["showcase_invoice.name is required:true — the cheapest deterministic VALIDATION_FAILED", "a member persona lacking showcase_announcement create (member_default: create false) — the deterministic 403"] + "requires": [ + "showcase_invoice.name is required:true — the cheapest deterministic VALIDATION_FAILED", + "a member persona lacking showcase_announcement create (member_default: create false) — the deterministic 403" + ] }, "steps": [ "boot showcase isolated; induce one error per family and capture the FULL body + status each time:", @@ -361,12 +502,35 @@ "evidence": "run record" } ], - "negative": ["any sampled failure answering 500 INTERNAL/UNCLASSIFIED for input the CALLER can fix is a FAIL of this item (the #5869 class — a server-fault code for a client mistake); likewise a 2xx on any of the induced-error probes is a FAIL (silent success)"], - "variants": ["family:validation", "family:auth-401", "family:authz-403", "family:not-found-object", "family:not-found-record", "family:batch-extension-code", "family:query-rejection"], - "traps": ["dispatcher-vs-hono-route"], - "source": ["packages/spec/src/api/error-code-ledger.zod.ts (ADR-0112 D3)", "packages/spec/src/api/errors.zod.ts (StandardErrorCode, FieldErrorCode, HttpStatusErrorCodeMap)", "packages/spec/src/shared/error-map.zod.ts", "packages/rest/src/rest-route-ledger.ts (sampled families)", "ADR-0114 D2/D4"], + "negative": [ + "any sampled failure answering 500 INTERNAL/UNCLASSIFIED for input the CALLER can fix is a FAIL of this item (the #5869 class — a server-fault code for a client mistake); likewise a 2xx on any of the induced-error probes is a FAIL (silent success)" + ], + "variants": [ + "family:validation", + "family:auth-401", + "family:authz-403", + "family:not-found-object", + "family:not-found-record", + "family:batch-extension-code", + "family:query-rejection" + ], + "traps": [ + "dispatcher-vs-hono-route" + ], + "source": [ + "packages/spec/src/api/error-code-ledger.zod.ts (ADR-0112 D3)", + "packages/spec/src/api/errors.zod.ts (StandardErrorCode, FieldErrorCode, HttpStatusErrorCodeMap)", + "packages/spec/src/shared/error-map.zod.ts", + "packages/rest/src/rest-route-ledger.ts (sampled families)", + "ADR-0114 D2/D4" + ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "new — error-envelope conformance sampling grounded in the two-tier code ledger, per the deep-test contract", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "new — error-envelope conformance sampling grounded in the two-tier code ledger, per the deep-test contract", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -377,10 +541,16 @@ "revision": 1, "priority": "P1", "surface": "api", - "personas": ["a baseline member (bulk writes on showcase_private_note — also exercises the owner-scoping of #2982)", "admin (cap and parity runs)"], + "personas": [ + "a baseline member (bulk writes on showcase_private_note — also exercises the owner-scoping of #2982)", + "admin (cap and parity runs)" + ], "fixtures": { "app": "showcase", - "requires": ["showcase_private_note (baseline create/edit — the bulk probe object)", "default maxBatchSize 200 unless overridden"] + "requires": [ + "showcase_private_note (baseline create/edit — the bulk probe object)", + "default maxBatchSize 200 unless overridden" + ] }, "steps": [ "boot showcase isolated; as the member POST /api/v1/data/showcase_private_note/createMany with records: [3 notes with unique-marker titles]; GET them back", @@ -429,32 +599,62 @@ "evidence": "responses + log excerpt" } ], - "negative": ["run the updateMany-matching-nothing probe as a persona with rows it CANNOT see and confirm the invisible rows are not counted or mutated (bulk writes are RLS-scoped, #2982) — a nonzero affected-count there is a security FAIL, not a bookkeeping quirk"], - "variants": ["door:createMany", "door:updateMany", "door:deleteMany", "door:per-object-batch (POST /data/:object/batch)"], - "automated": { "kind": "dogfood", "ref": "packages/qa/dogfood/test/owner-anchor-and-bulk-writes.dogfood.test.ts (bulk halves)" }, - "traps": ["wrong-persona"], - "source": ["packages/rest/src/rest-route-ledger.ts (batch family — the four doors)", "packages/spec/src/api/batch.zod.ts", "packages/spec/src/api/error-code-ledger.zod.ts (BATCH_TOO_LARGE, ERR_BULK_RESULT_MISMATCH)", "#2982", "release-15.1 plan §A3/A4"], + "negative": [ + "run the updateMany-matching-nothing probe as a persona with rows it CANNOT see and confirm the invisible rows are not counted or mutated (bulk writes are RLS-scoped, #2982) — a nonzero affected-count there is a security FAIL, not a bookkeeping quirk" + ], + "variants": [ + "door:createMany", + "door:updateMany", + "door:deleteMany", + "door:per-object-batch (POST /data/:object/batch)" + ], + "automated": { + "kind": "dogfood", + "ref": "packages/qa/dogfood/test/owner-anchor-and-bulk-writes.dogfood.test.ts (bulk halves)" + }, + "traps": [ + "wrong-persona" + ], + "source": [ + "packages/rest/src/rest-route-ledger.ts (batch family — the four doors)", + "packages/spec/src/api/batch.zod.ts", + "packages/spec/src/api/error-code-ledger.zod.ts (BATCH_TOO_LARGE, ERR_BULK_RESULT_MISMATCH)", + "#2982", + "release-15.1 plan §A3/A4" + ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "new — bulk-write contract item (boundaries, parity, per-row outcomes), per the deep-test contract", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "new — bulk-write contract item (boundaries, parity, per-row outcomes), per the deep-test contract", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { "id": "api-backend.route-ledger-live-parity", - "title": "Every REST_ROUTE_LEDGER family is actually mounted on the live server — no route that exists only in unit tests", + "title": "Every ledgered route family — REST, dispatcher, auth, storage/i18n services — and the non-ledgered mounts (/api/settings, /api/v1/datasources) are actually mounted on the live server — no route that exists only in unit tests", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", "surface": "api", - "personas": ["admin (authenticated, so a 401 cannot mask a 404)"], + "personas": [ + "admin (authenticated, so a 401 cannot mask a 404)" + ], "fixtures": { "app": "showcase", - "requires": ["packages/rest/src/rest-route-ledger.ts — the audited route inventory (route-manager + direct-mount rows, full wire paths at /api/v1)"] + "requires": [ + "packages/rest/src/rest-route-ledger.ts — the audited route inventory (route-manager + direct-mount rows, full wire paths at /api/v1)" + ] }, "steps": [ "boot the real HTTP stack (os dev); read REST_ROUTE_LEDGER and pick at least one representative route per family (discovery, openapi, metadata, ui, crud, batch, data-actions, forms, search, security, security-explain, record-shares, sharing-rules, reports, approvals, email, analytics, packages, external-datasource)", "fire each sampled route as admin with a minimal-valid shape (GETs verbatim; parameterized routes filled with real seeded names, e.g. GET /api/v1/data/showcase_task, GET /api/v1/meta/object/showcase_task, GET /api/v1/security/explain)", "capture status + code per route", + "read the OTHER ledgers and fire one representative route each: the dispatcher ledger (packages/runtime/src/route-ledger.ts) families share-links/keys/notifications/suggested-bindings/i18n/analytics (e.g. GET /api/v1/share-links, POST /api/v1/keys, GET /api/v1/notifications, GET /api/v1/security/suggested-bindings, GET /api/v1/i18n/locales, POST /api/v1/analytics/query), AUTH_ROUTE_LEDGER (GET /api/v1/auth/get-session), the storage + i18n service ledgers", + "fire the NON-LEDGERED mounts: GET /api/settings (note the /api/settings base — NOT /api/v1), GET /api/v1/datasources/drivers, GET /api/v1/datasources", + "fire the dispatcher meta state route: GET /api/v1/meta/objects/showcase_task/state/status?from=in_review, the same with ?from omitted, and GET /api/v1/meta/objects/not_a_real_object/state/status as the 404 control", "fire one deliberately-unmounted path (GET /api/v1/definitely-not-a-route) as the 404 control", "compare GET /api/v1/discovery capability bits against the families that answered (search/export/transactionalBatch at minimum)" ], @@ -488,15 +688,355 @@ "oracle": "api", "verify": "at least one direct-mount route in the sample set answers non-routing-404", "evidence": "trace" + }, + { + "clause": "the sweep reaches the OTHER ledgers, not just the 19 REST families: the DISPATCHER ledger's representative routes each answer non-routing-404 on the live hono server — share-links (GET /api/v1/share-links), keys (POST /api/v1/keys), notifications (GET /api/v1/notifications), suggested-bindings (GET /api/v1/security/suggested-bindings), i18n (GET /api/v1/i18n/locales), analytics (POST /api/v1/analytics/query, capability-conditional) — the #3361 dispatcher-vs-hono class this item exists for, now covered on the dispatcher's OWN table (packages/runtime/src/route-ledger.ts), not just the REST one", + "oracle": "api", + "verify": "per-route status table for the six dispatcher families vs the 404 control; a dispatcher route answering the routing-404 shape is a FAIL (dispatcher-mounted-in-tests-only)", + "evidence": "the dispatcher status table + control trace" + }, + { + "clause": "the auth and service ledgers are swept too: a representative route from AUTH_ROUTE_LEDGER (packages/plugins/plugin-auth/src/auth-route-ledger.ts — e.g. GET /api/v1/auth/get-session), the storage service ledger (packages/services/service-storage/src/storage-route-ledger.ts) and the i18n service ledger (packages/services/service-i18n/src/i18n-route-ledger.ts) each answer non-routing-404 — the three surfaces #3636 ledgered OUTSIDE @objectstack/rest", + "oracle": "api", + "verify": "one representative route per ledger answers non-404; read each ledger for its own representative route rather than guessing paths", + "evidence": "the per-ledger traces" + }, + { + "clause": "the NON-LEDGERED mounts answer too, and their absence from any route ledger is recorded as the finding: GET /api/settings (service-settings, mounted at /api/settings — NOT under /api/v1) answers non-404, and GET /api/v1/datasources/drivers (always-available static catalog) plus GET /api/v1/datasources (200, or 503 SERVICE_UNAVAILABLE when the admin service is unwired) answer non-404 — yet neither /api/settings nor the /api/v1/datasources admin CRUD appears in packages/rest/src/rest-route-ledger.ts (the tranche-3 route-ledger discipline gap, PENDING-GAPS §E)", + "oracle": "api", + "verify": "the /api/settings and /api/v1/datasources traces are non-routing-404; the run record notes both mounts are unledgered", + "evidence": "the two traces + the unledgered-mount finding" + }, + { + "clause": "the dispatcher meta state route is live AND correct: GET /api/v1/meta/objects/showcase_task/state/status?from=in_review (dispatcher ledger meta.getLegalNextStates, ADR-0020 D3.3) answers non-404 and returns next == ['done','in_progress'] — exactly the declared task_status_flow transition set for that state; ?from omitted returns next:null (no from ⇒ no transition table), a field with no FSM returns next:null, and an unknown object → 404", + "oracle": "api", + "verify": "the state-route response's next[] equals the object's state_machine transitions for the from-state (examples/app-showcase/src/data/objects/task.object.ts task_status_flow: in_review → [done, in_progress]); the null/404 controls hold", + "evidence": "the state-route responses (from=in_review, from-omitted, unknown-object) vs the declared transitions" } ], - "negative": ["do not tick this from RestServer.getRoutes() output or unit tests — the item exists because that oracle lied (#3361, dispatcher-vs-hono-route); the only admissible evidence is live HTTP traces from the running server"], - "variants": ["discovery", "openapi", "metadata", "ui", "crud", "batch", "data-actions", "forms", "search", "security", "security-explain", "record-shares", "sharing-rules", "reports", "approvals", "email", "analytics", "packages (direct-mount)", "external-datasource (direct-mount)"], - "traps": ["dispatcher-vs-hono-route"], - "source": ["packages/rest/src/rest-route-ledger.ts (variant source — the 19 families)", "packages/rest/src/rest-route-ledger.conformance.test.ts", "#3587", "#3361"], + "negative": [ + "do not tick this from RestServer.getRoutes() output or unit tests — the item exists because that oracle lied (#3361, dispatcher-vs-hono-route); the only admissible evidence is live HTTP traces from the running server" + ], + "variants": [ + "discovery", + "openapi", + "metadata", + "ui", + "crud", + "batch", + "data-actions", + "forms", + "search", + "security", + "security-explain", + "record-shares", + "sharing-rules", + "reports", + "approvals", + "email", + "analytics", + "packages (direct-mount)", + "external-datasource (direct-mount)", + "dispatcher:share-links", + "dispatcher:keys", + "dispatcher:notifications", + "dispatcher:suggested-bindings", + "dispatcher:i18n", + "dispatcher:analytics (capability-conditional)", + "dispatcher:meta-state-route (meta.getLegalNextStates)", + "ledger:auth (AUTH_ROUTE_LEDGER)", + "ledger:storage-service", + "ledger:i18n-service", + "unledgered:/api/settings", + "unledgered:/api/v1/datasources" + ], + "traps": [ + "dispatcher-vs-hono-route" + ], + "source": [ + "packages/rest/src/rest-route-ledger.ts (variant source — the 19 REST families)", + "packages/rest/src/rest-route-ledger.conformance.test.ts", + "packages/runtime/src/route-ledger.ts (dispatcher ledger — share-links/keys/notifications/suggested-bindings/i18n/analytics families + the meta.getLegalNextStates state route)", + "packages/plugins/plugin-auth/src/auth-route-ledger.ts (AUTH_ROUTE_LEDGER — the enumerated better-auth table, #3656)", + "packages/services/service-storage/src/storage-route-ledger.ts + packages/services/service-i18n/src/i18n-route-ledger.ts (tranche-3 per-service ledgers, #3636)", + "packages/services/service-settings/src/settings-routes.ts (/api/settings — non-/api/v1 mount, UNLEDGERED)", + "packages/services/service-datasource/src/admin-routes.ts (/api/v1/datasources admin CRUD — UNLEDGERED, tranche-3 gap)", + "examples/app-showcase/src/data/objects/task.object.ts (task_status_flow transitions for the meta state-route clause)", + "docs/qa/platform-checklist/PENDING-GAPS.md §D/§E", + "#3587", + "#3361" + ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "new — live-mount parity sweep over the audited route ledger, per the deep-test contract", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "new — live-mount parity sweep over the audited route ledger, per the deep-test contract", + "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 2, + "date": "2026-08-08", + "change": "extended the sweep beyond the 19 REST families to the OTHER ledgers + non-ledgered mounts: dispatcher ledger (share-links/keys/notifications/suggested-bindings/i18n/analytics), AUTH_ROUTE_LEDGER, storage/i18n service ledgers, /api/settings, /api/v1/datasources; added a live-mount clause per ledger with a 404 control (the #3361 dispatcher-vs-hono class), and folded in the meta.getLegalNextStates state route (legal next states == declared task_status_flow set) since api-backend is its natural home", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "api-backend.declarative-endpoint-execution", + "title": "Metadata-authored `api` endpoints mount as real URLs with their declared auth/cache policy", + "since": "v17", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": [ + "admin", + "anonymous" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the two live declarative endpoints in examples/app-showcase/src/system/apis/index.ts (a GET task feed at /api/v1/apps/showcase/tasks and a flow-delegating endpoint) — ADR-0121 / #5040" + ] + }, + "variants": [ + "object_operation target — the task feed (authed read)", + "flow target — delegates to a flow", + "policy: authRequired (default true)", + "policy: cacheTtl → Cache-Control", + "script/proxy target — answer 501 in the open framework" + ], + "steps": [ + "boot showcase isolated; sign in as admin", + "GET /api/v1/apps/showcase/tasks authed; capture status, body, and response headers", + "GET the same endpoint unauthenticated; capture status", + "author a script-target endpoint shape in a scratch package and hit it; capture the 501", + "GET /apps/showcase/nope (no matching declaration); capture the transport 404" + ], + "acceptance": [ + { + "clause": "the object_operation endpoint answers 200 authed with the delegated data, and carries the declared cache policy (Cache-Control: private, max-age=30 when cacheTtl is set)", + "oracle": "api", + "verify": "authed GET status 200 + body + Cache-Control header vs the declared policy", + "evidence": "response + headers" + }, + { + "clause": "authRequired defaults true — the anonymous call is refused 401 UNAUTHENTICATED, not served", + "oracle": "api", + "verify": "unauth GET status 401 + code", + "evidence": "the response" + }, + { + "clause": "a script/proxy target answers 501 in the open framework (declared ≠ delivered surfaces honestly, not a fake 200)", + "oracle": "api", + "verify": "the 501 envelope", + "evidence": "the response" + }, + { + "clause": "an unmatched path under /apps/** falls through to the transport 404 — the notFound seam does not swallow it", + "oracle": "api", + "verify": "GET a nonexistent app path → 404", + "evidence": "the response" + } + ], + "negative": [ + "a declarative endpoint serving anonymously when authRequired is true (or unset) is a FAIL — the default is closed" + ], + "automated": { + "kind": "dogfood", + "ref": "packages/qa/dogfood/test/showcase-declarative-endpoints.dogfood.test.ts + declarative-endpoint-policy.dogfood.test.ts" + }, + "traps": [ + "dispatcher-vs-hono-route" + ], + "source": [ + "examples/app-showcase/src/system/apis/index.ts (the two live endpoints)", + "ADR-0121 / #5040 (declarative endpoint E-series)", + "packages/runtime notFound-fallback mount seam" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — the coverage.json `api` waiver was STALE (showcase authors two live endpoints with dogfood pins); un-waived", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "api-backend.package-rest-lifecycle", + "title": "Package REST lifecycle: POST /packages creates (201), a duplicate name is refused 409 (no silent manifest clobber), PATCH partial-patches the manifest, and an explicit overwrite re-install replaces in place without duplicating", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "api", + "personas": [ + "admin (manage_metadata / package authoring)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "a runtime that accepts package install/patch over HTTP (os dev's dispatcher install route, packages.install → POST /api/v1/packages); a scratch package id (e.g. qa_pkg_lifecycle_probe) so no shipped package is mutated" + ], + "knownGaps": [ + "if the deployment blocks runtime package install (read-only metadata), record it as a fixture requirement and treat the item blocked(environment) rather than failing the install probes" + ] + }, + "steps": [ + "boot showcase isolated; sign in as admin; choose a scratch id qa_pkg_lifecycle_probe", + "POST /api/v1/packages with { manifest: { id: 'qa_pkg_lifecycle_probe', name: 'QA Probe', version: '1.0.0', scope: 'custom', type: 'app' } }; capture status + body; then GET /api/v1/packages/qa_pkg_lifecycle_probe to confirm it persisted (the dispatcher install lands in BOTH the in-memory registry and durable sys_packages)", + "POST the SAME manifest again with NO overwrite flag; capture status + body", + "PATCH /api/v1/packages/qa_pkg_lifecycle_probe with { name: 'QA Probe Renamed', description: 'edited', version: '1.1.0' }; GET the package back and confirm the patch persisted and that id/scope/type and the lifecycle fields (enabled/status/installedAt) are untouched", + "PATCH three malformed bodies: { name: '' }, { version: 'not-semver' }, and {} (nothing to update); capture each rejection", + "re-install with the explicit opt-in: POST /api/v1/packages?overwrite=true (or body { overwrite: true }) carrying the same id and a changed manifest (version 2.0.0); capture status", + "GET /api/v1/packages and count rows whose id == qa_pkg_lifecycle_probe — must be exactly one", + "confirm the LIVE routing seam: POST /api/v1/packages resolves to the dispatcher install route (REST moved marketplace publish OFF the bare path to POST /api/v1/packages/publish in #3610), and PATCH /api/v1/packages/:id answers non-404 though it is absent from packages/rest/src/rest-route-ledger.ts (dispatcher-only)" + ], + "acceptance": [ + { + "clause": "a scratch package installs → 201: POST /api/v1/packages returns 201 with the created package, and a follow-up GET reads it back (the install lands in the in-memory registry AND durable sys_packages, per packages.ts routing through protocol.installPackage)", + "oracle": "api", + "verify": "POST status 201; GET /api/v1/packages/qa_pkg_lifecycle_probe returns the package", + "evidence": "the POST + GET responses" + }, + { + "clause": "a duplicate name is refused 409, NEVER silently overwritten: the second POST (no overwrite) answers 409 with message \"Package 'qa_pkg_lifecycle_probe' already exists\" and the bare-409 derived code RESOURCE_CONFLICT (HttpStatusErrorCodeMap 409) — the #2995 data-loss footgun (a silent re-install destroying the existing manifest) is closed", + "oracle": "api", + "verify": "step-3 response: status 409, code RESOURCE_CONFLICT, message names the existing id; the stored manifest is unchanged from step 2", + "evidence": "the 409 response + a re-read proving the manifest survived" + }, + { + "clause": "PATCH is a real partial patch: only name/description/version present are changed and read back; identity (id/scope/type) and lifecycle (enabled/status/installedAt) are preserved — a PATCH is not a full replace", + "oracle": "api", + "verify": "step-4 GET: the three patched fields updated, the identity+lifecycle fields byte-identical to before the PATCH", + "evidence": "before/after package reads" + }, + { + "clause": "PATCH validates its inputs: an empty name → 400 ('name must not be empty'), a non-semver version → 400 ('version must be semantic (e.g. 1.0.0)'), and a nothing-to-update body → 400 — each a caller-fixable 400, never a 500", + "oracle": "api", + "verify": "the three step-5 responses are 400 with the located messages", + "evidence": "the three rejections" + }, + { + "clause": "the explicit overwrite re-install replaces in place WITHOUT duplicating: POST with overwrite=true (body or query) succeeds and the subsequent GET /api/v1/packages lists exactly ONE row for the id — the overwrite is the deliberate opt-out of the 409 guard, and it never leaves two package rows behind", + "oracle": "api", + "verify": "step-6 status is a success (201/200); step-7 count of qa_pkg_lifecycle_probe rows == 1; the manifest reflects the overwrite (version 2.0.0)", + "evidence": "the overwrite response + the deduped list" + }, + { + "clause": "the sweep is against the LIVE server, not one ledger: POST /api/v1/packages is served by the dispatcher install route (not the REST marketplace publish moved to /packages/publish in #3610), and PATCH /api/v1/packages/:id answers non-404 despite being absent from the REST route ledger", + "oracle": "api", + "verify": "POST /api/v1/packages installs (not a 400 publish-shape rejection); PATCH /api/v1/packages/:id answers non-routing-404", + "evidence": "the two traces" + } + ], + "negative": [ + "a second POST of the same id answering 2xx and silently replacing the manifest is the #2995 data-loss FAIL (the whole reason the 409 guard exists)", + "a PATCH that resets enabled/status/installedAt (a full replace masquerading as a partial patch) is a FAIL", + "an overwrite re-install that leaves two rows for one id in GET /api/v1/packages is a FAIL (duplication, not replacement)" + ], + "traps": [ + "dispatcher-vs-hono-route" + ], + "source": [ + "packages/runtime/src/domains/packages.ts (POST install: pkgId-required 400, 409-duplicate guard + overwrite opt-in #2995; PATCH partial-patch validators; routes through protocol.installPackage / protocol.updatePackage)", + "packages/runtime/src/route-ledger.ts (POST /packages → packages.install, PATCH /packages/:id → packages.update, GET /packages → packages.list)", + "packages/rest/src/rest-route-ledger.ts (POST /api/v1/packages/publish moved off the bare POST /packages in #3610; GET/DELETE /packages/:id direct-mount shadows)", + "packages/spec/src/api/errors.zod.ts (HttpStatusErrorCodeMap 409 → RESOURCE_CONFLICT)", + "PENDING-GAPS §G3 (#2995 dup-clobber, #2971, #3007)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "new — package REST lifecycle (create/409-dup/patch/overwrite-reinstall), grounded in the dispatcher packages domain and the 409 data-loss guard; per PENDING-GAPS §G3", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "api-backend.api-console-discovery-execute", + "title": "Studio Developer API Console: the endpoint tree mirrors /discovery, a seeded-object GET executes with live JSON + status + timing, and a malformed body surfaces the server error envelope verbatim", + "since": "v17", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "personas": [ + "admin" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the console Developer hub reachable (objectui apps/console/src/pages/developer/ApiConsolePage.tsx, developer:api-console route)", + "stock showcase seed with showcase_task rows (the seeded object the GET executes against)" + ] + }, + "steps": [ + "boot showcase isolated; sign in as admin; open Studio → Developer → API Console", + "GET /api/v1/discovery out of band and record its services + routes maps — the ground truth the tree is gated on (useApiDiscovery gates each service group on isServiceUsable(discovery.services[name]), ADR-0076 D12)", + "screenshot the endpoint tree AFTER the 'Discovering APIs' spinner settles; only then read the DOM", + "spot-check the tree against REST_ROUTE_LEDGER families: a seeded object (showcase_task) shows its CRUD family (GET/POST/PATCH/DELETE /api/v1/data/showcase_task) under a Data group, the Metadata group lists /api/v1/meta/object/showcase_task, System lists discovery/packages/health", + "select GET /api/v1/data/showcase_task and click Send; capture the response pane: status code, the JSON body, and the duration (ms)", + "cross-check the pane's JSON against a direct GET of the same URL (the page fetches `${client.baseUrl}${url}` — the same wire call)", + "select POST /api/v1/data/showcase_task, enter a MALFORMED body (invalid JSON, or a body missing the required fields), and Send; capture the response pane" + ], + "acceptance": [ + { + "clause": "the tree mirrors /discovery: every rendered service group corresponds to a service the /discovery payload reports usable (isServiceUsable) — a stub/unavailable service renders NO group — and a seeded object's CRUD + meta families appear, spot-checked against REST_ROUTE_LEDGER", + "oracle": "screenshot", + "verify": "the tree screenshot cross-checked name-by-name against the pre-captured /discovery services map; the showcase_task CRUD + meta entries are present", + "evidence": "tree screenshot + the /discovery body, diffed" + }, + { + "clause": "a seeded-object GET executes to LIVE JSON + status + timing: Send on GET /api/v1/data/showcase_task yields status 200, a JSON body of real seeded rows, and a duration reading — and the body matches a direct GET of the same URL", + "oracle": "network", + "verify": "response-pane status 200 + JSON rows == direct GET; the pane shows a non-zero ms duration", + "evidence": "the response-pane screenshot + the direct GET response" + }, + { + "clause": "status + timing are the REAL response's, not faked: the pane's status code (and its color band) and the ms figure are computed from the actual fetch (performance.now delta) — a 4xx endpoint shows its real 4xx, a network failure shows status 0 (the page's own catch), distinguishable from a server envelope", + "oracle": "network", + "verify": "drive one endpoint that answers 4xx and confirm the pane shows that 4xx status, not a 200", + "evidence": "the 4xx pane + its trace" + }, + { + "clause": "a malformed body surfaces the server error envelope VERBATIM: an invalid/under-specified POST body renders the server's standard error envelope (ledgered code + message) in the response pane exactly as returned — not a swallowed client-side error, not a fabricated success", + "oracle": "network", + "verify": "the malformed POST's pane shows the server's 400 envelope (code ∈ the ledger, e.g. VALIDATION_FAILED) identical to the raw HTTP response", + "evidence": "the pane + the raw response body" + }, + { + "clause": "the discovery→console pipeline reaches the request form: the tree is searchable/collapsible and selecting an endpoint populates method + URL + any body template — read the DOM only after the screenshot confirms the pane rendered", + "oracle": "dom", + "verify": "selecting an endpoint sets the method dropdown and URL input; the body textarea appears for POST/PATCH/PUT", + "evidence": "DOM excerpt after the render screenshot" + } + ], + "negative": [ + "a tree entry for a service /discovery reports unusable (stub/unavailable) is a FAIL — the discovery payload is the authority, not a hardcoded catalog", + "a response pane showing a 2xx/blank for a request the server actually refused (or masking the server envelope behind a generic client message) is a FAIL" + ], + "traps": [ + "stale-console-bundle", + "hydration-race", + "wrong-panel" + ], + "source": [ + "objectui apps/console/src/pages/developer/ApiConsolePage.tsx (the API console: raw fetch to `${client.baseUrl}${url}`, status/duration/JSON pane, request history)", + "objectui apps/console/src/pages/developer/hooks/useApiDiscovery.ts (tree built from GET /api/v1/discovery services/routes + client.meta types/objects; isServiceUsable gate per ADR-0076 D12)", + "packages/rest/src/rest-route-ledger.ts (the REST families the tree is spot-checked against)", + "packages/spec/src/api/discovery.zod.ts (the discovery payload)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "new — Studio Developer API Console: discovery-mirrored tree, live GET execution (JSON+status+timing), malformed-body envelope passthrough; per PENDING-GAPS §C", + "ref": "claude/platform-test-checklist-ocwugl" + } ] } ] -} +} \ No newline at end of file diff --git a/docs/qa/platform-checklist/areas/approvals.json b/docs/qa/platform-checklist/areas/approvals.json index 3f89ba72bf..e5fb16b1e9 100644 --- a/docs/qa/platform-checklist/areas/approvals.json +++ b/docs/qa/platform-checklist/areas/approvals.json @@ -583,6 +583,238 @@ "history": [ { "revision": 1, "date": "2026-08-07", "change": "initial — covers the #3447 dynamic-routing chain (typed decision outputs → vars → expression approvers) with the required-output gate", "ref": "claude/platform-test-checklist-ocwugl" } ] + }, + { + "id": "approvals.ooo-delegation-reroute", + "title": "An active out-of-office delegation reroutes an individually-routed approver to the delegate; expiring the window hands the slot back", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": ["dev admin (the DELEGATE B — signed in, so they can actually decide the rerouted request)", "a routable non-admin delegator A (sys_user row, e.g. Mei Phone usr_showcase_phone_demo)"], + "fixtures": { + "app": "showcase", + "requires": [ + "a flow with an INDIVIDUALLY-routed approver — OOO delegation only applies to type user / field / manager (ApprovalService.expandApprovers), NOT position. No ACTIVE seeded showcase flow routes individually (they all route by position), so author a scratch autolaunched flow in a WRITABLE package with a single approval node config.approvers=[{type:'user', value:''}], shaped after examples/app-showcase/src/automation/flows/approver-bindings.flow.ts (the record-backed approver specimen)", + "delegator A = a routable sys_user row that is NOT the signed-in admin (Mei Phone usr_showcase_phone_demo); delegate B = the dev admin" + ], + "knownGaps": [ + "the delegate must be the SIGNED-IN admin (B) so the rerouted request is decidable without provisioning a second better-auth account — the same sign-in gap approvals.per-group-signoff records for Ada; routing the scratch flow at a non-admin A and delegating A→admin sidesteps it", + "authoring the scratch flow needs a writable/scratch package — the showcase ships read-only" + ] + }, + "steps": [ + "boot showcase isolated (dogfood §0); sign in as the dev admin (the delegate B)", + "in a writable/scratch package author + register (POST /api/v1/automation) an autolaunched flow with a single approval node whose approvers=[{type:'user', value:''}], behavior first_response", + "baseline (no delegation): trigger the flow (POST /api/v1/automation//trigger); GET /api/v1/approvals/requests?status=pending, find the new request, GET /:id — pending_approvers must be [A]; GET /:id/actions shows NO ooo_substitute row", + "create an ACTIVE delegation: POST /api/v1/data/sys_approval_delegation {delegator_id:'', delegate_id:'', valid_from:, valid_until:, reason:'Annual leave'}", + "trigger the flow AGAIN; GET the new request /:id — pending_approvers must now be [B], not [A]; GET /:id/actions — one row action='ooo_substitute' whose comment names 'A → B'", + "read the delegate's inbox/notifications — a topic approval.ooo_substituted notification addressed to B, actionUrl /system/approvals (#1322 M4)", + "decide as B: POST /api/v1/approvals/requests/:id/approve; GET /:id/actions — the approve row's actor is B (the delegate acts under their OWN identity — nothing impersonated as A)", + "expire the window: PATCH the delegation valid_until to a past instant (or DELETE it); trigger the flow ONCE more; GET the newest request /:id — pending_approvers is [A] again and its /actions carries NO ooo_substitute row" + ], + "acceptance": [ + { + "clause": "baseline: with no active delegation the individually-routed request resolves pending_approvers = [A]", + "oracle": "api", + "verify": "GET /:id on the pre-delegation request: pending_approvers == [''] and /:id/actions has no action='ooo_substitute' row", + "evidence": "baseline request + actions reads" + }, + { + "clause": "an active A→B delegation reroutes the slot: a fresh request resolves pending_approvers = [B] (not A) and records the substitution on the audit trail", + "oracle": "api", + "verify": "GET /:id after the delegation exists: pending_approvers == ['']; GET /:id/actions carries exactly one action='ooo_substitute' row (actor_id null — a system action) whose comment reads ''", + "evidence": "post-delegation request + actions reads" + }, + { + "clause": "the substitution notifies the delegate (M4): B receives an approval.ooo_substituted inbox notification deep-linking the approvals inbox", + "oracle": "api", + "verify": "the delegate's notification/inbox read contains a topic approval.ooo_substituted row for this request id with actionUrl containing /system/approvals", + "evidence": "notification read" + }, + { + "clause": "the delegate decides under their OWN identity — the audit stays honest, nothing is impersonated as the delegator", + "oracle": "api", + "verify": "B's /approve succeeds and finalizes the request; the recorded approve action's actor_id is B, never A (the delegate becomes a real pending approver, ApprovalService docstring)", + "evidence": "decision POST + the approve action row" + }, + { + "clause": "the window is enforced at RESOLUTION time (isGrantActive, ADR-0091 D2), not by a job: after the window expires a fresh request routes back to A with no substitution", + "oracle": "api", + "verify": "with valid_until in the past, GET /:id on the newest request: pending_approvers == [''] and /:id/actions has no ooo_substitute row — B has lost the power the instant the window closed", + "evidence": "post-expiry request + actions reads" + } + ], + "negative": [ + "an approval action attributed to A while B was the one who clicked is a FAIL — the delegate acts under their own identity and the audit must not launder the decision back onto the out-of-office user", + "a request that still reroutes to B after the window has expired (or before valid_from) is a FAIL — validity is a half-open [from, until) window enforced at resolution, never a background job that could lag", + "a delegation on a POSITION-routed slot that reroutes is out of contract — OOO applies only to individually-routed (user/field/manager) approvers; a per-group/position node must be unaffected" + ], + "traps": ["wrong-persona", "seed-data-thin"], + "source": [ + "packages/plugins/plugin-approvals/src/sys-approval-delegation.object.ts (#1322 M1 — self-service OOO rule, half-open UTC window, resolution-time enforcement)", + "packages/plugins/plugin-approvals/src/approval-service.ts (applyOooDelegation + lookupActiveDelegation — individually-routed only; M4 ooo_substitute audit row + approval.ooo_substituted / approval.ooo_skipped notifications)", + "@objectstack/core isGrantActive (ADR-0091 D2 half-open validity predicate)", + "examples/app-showcase/src/automation/flows/approver-bindings.flow.ts (the {type:'user'|'manager'|'field'} approver specimens the scratch flow is shaped after)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "initial — pins the #1322 OOO delegation reroute: active A→B window reroutes an individually-routed slot to B (audited + notified, decided under B's own identity); expiry hands it back to A at resolution time", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "approvals.record-page-decisions", + "title": "Approve/Reject render in the record header; Reject fires after ONE dialog; a locking approval hides inline edit", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "personas": ["dev admin (holds the manager position, so showcase_budget_approval's manager_review routes to them — a pending approver standing on the record page)"], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_budget_approval raised on a showcase_project by PATCHing budget above 100000 (budget != previous.budget) — parks at manager_review (approvers position 'manager' = the admin), lockRecord:false; driving budget above 500000 reaches exec_review, lockRecord:true (the locking contrast, objectui#2902)", + "the record page surface: objectui packages/app-shell RecordDetailView + useRecordApprovals + buildApprovalDecisionActions" + ] + }, + "steps": [ + "boot showcase isolated (dogfood §0); sign in as the dev admin", + "PATCH a showcase_project budget to e.g. 200000 over /api/v1/data — showcase_budget_approval opens and parks at manager_review (routed to manager = admin), lockRecord:false", + "open that project's record page (/apps/showcase_app/showcase_project/:id); screenshot the header — Approve and Reject must render in the primary slot (buildApprovalDecisionActions: Approve variant 'primary' order -100, Reject variant 'destructive' order -99, both locations ['record_header'])", + "because manager_review is lockRecord:false, confirm inline-edit affordances remain live on this record (the editable contrast)", + "click Reject: assert exactly ONE dialog opens — the param dialog carrying the comment textarea inline, titled by the Reject label with the confirm question as its description; there is NO separate confirm-then-comment second dialog (objectui#3126)", + "fill the comment and Confirm; capture POST /api/v1/approvals/requests/:id/reject and re-read the request (status=rejected)", + "approve path: raise another budget-approval request; on its record page click Approve → one dialog with an optional comment → Confirm → POST /:id/approve → re-read (status=approved)", + "lock contrast: drive a >500000 budget to exec_review (lockRecord:true) on a project record page; screenshot — the inline-edit affordances are HIDDEN (recordLockedByApproval → canEdit suppressed)", + "both-sides gate: open a project record page whose pending approval routes AWAY from the admin (or has none) — the header offers NO Approve/Reject (approvals.canDecide false)" + ], + "acceptance": [ + { + "clause": "Approve and Reject render in the record header primary slot for the pending approver", + "oracle": "screenshot", + "verify": "after the header renders, screenshot shows Approve (primary) ahead of app record_header actions and Reject (destructive) — the strongly-negative order floats the decision buttons into the primary slot (buildApprovalDecisionActions #2670/objectui#2339)", + "evidence": "record-header screenshot" + }, + { + "clause": "Reject fires after exactly ONE dialog: the comment param is collected INLINE in that single dialog and Confirm POSTs the reject — no second, unexpected comment dialog", + "oracle": "network", + "verify": "one param dialog (no chained confirm dialog) precedes exactly one POST /api/v1/approvals/requests/:id/reject; the objectui#3126 double-dialog bug (confirm then a second comment dialog, decision silently not sent) must not reproduce", + "evidence": "screenshot of the single dialog + the reject network trace" + }, + { + "clause": "the decision POSTs the approvals route and the request re-reads with the new status", + "oracle": "api", + "verify": "reject → GET /:id status=rejected; approve → status=approved; the record header collected the comment (rides actionParams, not the dead collectParams #2955) and it round-trips onto the action row", + "evidence": "before/after request reads + the decision action row" + }, + { + "clause": "a LOCKING pending approval hides the record's inline-edit affordances; a non-locking one keeps them", + "oracle": "screenshot", + "verify": "under exec_review (lockRecord:true) the inline-edit affordances are absent (recordLockedByApproval(pendingRequest) → canEdit false, objectui#2902); under manager_review (lockRecord:false) they remain — both sides, on the same flow", + "evidence": "two record-page screenshots (locked vs editable)" + }, + { + "clause": "the decision buttons are gated by the viewer's approver relationship — a record whose pending approval does not route to the viewer offers neither Approve nor Reject", + "oracle": "dom", + "verify": "after confirming render via screenshot, the header DOM has no approve_request/reject_request actions when approvals.canDecide is false (the current user is not in pending_approvers)", + "evidence": "screenshot + header action DOM list" + } + ], + "negative": [ + "a Reject that opens a SECOND dialog (a confirm dialog followed by a separate comment dialog) and silently no-ops after the first Confirm is a FAIL — the objectui#3126 regression: the param dialog IS the confirmation, and nothing posts until its own Confirm", + "an inline-edit affordance offered while a lockRecord:true approval is pending is a FAIL — the server would reject the save RECORD_LOCKED, and offering the edit is exactly the objectui#2902 mislabel", + "Approve/Reject rendered on the header for a viewer who is not a pending approver (canDecide false) is a FAIL even if the eventual POST would 403" + ], + "traps": ["automation-input", "hydration-race"], + "source": [ + "objectui packages/app-shell/src/views/RecordDetailView.tsx (buildApprovalDecisionActions — record_header Approve/Reject, single-dialog Reject #3126, actionParams comment #2955; approvalLocked/canEdit gating #2902)", + "objectui packages/app-shell/src/hooks/useRecordApprovals.ts (recordLockedByApproval, canDecide, /approvals/requests decide routes)", + "objectui packages/app-shell/src/views/RecordDetailView.approvalDecisionActions.test.tsx (the param-contract pin)", + "examples/app-showcase/src/automation/flows/index.ts (BudgetApprovalFlow — manager_review lockRecord:false / exec_review lockRecord:true on showcase_project)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "initial — the record-page approval surface (distinct from the inbox-drawer items): header Approve/Reject, one-dialog Reject (#3126), decision round-trip, and the lockRecord-driven inline-edit hide (#2902)", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "approvals.sla-escalation", + "title": "A node's SLA escalation fires once past its timeout — the declared action runs and an escalate timeline row lands", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "api", + "personas": ["dev admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "a scratch autolaunched flow in a WRITABLE package with an approval node carrying config.escalation {enabled:true, timeoutHours, action, escalateTo, notifySubmitter} (ApprovalEscalationSchema, packages/spec/src/automation/approval.zod.ts)", + "clock control OR fractional-hour support: timeoutHours has a min of 1 and the escalation sweep (ApprovalService.runEscalations, ESCALATION_JOB_NAME, ADR-0042) runs on an interval against this.clock.now(), so driving a request PAST its deadline needs an injected/advanced clock or a direct runEscalations() call with a clock whose now() is beyond slaDueAt = created_at + timeoutHours" + ], + "knownGaps": [ + "hour-granular SLAs are not drivable on stock fixtures in a single session: the minimum timeoutHours is 1 and the sweep uses real time, so reaching the deadline requires a timing harness (clock injection / controllable runEscalations) that stock showcase does not provide — the timeout-dependent clauses (2/3/4) run only under that harness; clauses 1 and 5 are runnable today" + ] + }, + "blocked": { "by": "fixture", "ref": "hour-granular SLA needs a clock-control / runEscalations timing harness (timeoutHours min 1, sweep on real-time interval) — no stock-fixture way to advance past the deadline in-session" }, + "variants": ["reassign", "auto_approve", "auto_reject", "notify"], + "steps": [ + "boot showcase isolated (dogfood §0); sign in as the dev admin", + "in a writable package author + register a flow whose approval node config.escalation = {enabled:true, timeoutHours:1, action:'reassign', escalateTo:'', notifySubmitter:true}", + "trigger the flow; GET /api/v1/approvals/requests/:id — status=pending and the request carries sla_due_at = created_at + timeoutHours (the SLA is materialized on open)", + "[needs clock control] advance the clock past sla_due_at (inject a clock / drive ApprovalService.runEscalations() with a clock whose now() is beyond the deadline) and run one escalation sweep", + "GET /:id/actions — assert exactly one action='escalate' row (the audit-first idempotency marker, actor SLA_ACTOR_ID) whose comment names the action", + "assert the declared action's effect: reassign → pending_approvers swapped to the escalatees + an approval.escalated notification to them; auto_approve/auto_reject → request finalized approved/rejected and the owning run resumes; notify → an approval.sla_breached notification to the pending approvers", + "with notifySubmitter!==false, read the submitter's inbox — an approval.sla_breached notification addressed to them", + "idempotency: run the sweep a SECOND time — GET /:id/actions shows NO second escalate row (single-shot, marker-guarded)", + "author-negative: build a scratch flow whose escalation carries an unknown key (or the `timeout`/`sla` alias) and validate it" + ], + "acceptance": [ + { + "clause": "the node's declared SLA is materialized on the pending request: sla_due_at = created_at + timeoutHours", + "oracle": "api", + "verify": "GET /:id right after the request opens: sla_due_at equals created_at plus timeoutHours (slaDueAt, packages/plugins/plugin-approvals/src/approval-service.ts) — this half is runnable without clock control", + "evidence": "request read with sla_due_at" + }, + { + "clause": "past the deadline the sweep escalates exactly ONCE: one action='escalate' timeline row, and a re-run adds none", + "oracle": "api", + "verify": "after advancing past sla_due_at and sweeping, GET /:id/actions has exactly one action='escalate' row (actor SLA_ACTOR_ID); a second sweep adds no further escalate row (the audit row is the idempotency marker, written before any mutation)", + "evidence": "actions reads after the first and second sweeps" + }, + { + "clause": "per-variant: the declared escalation action fires — reassign swaps pending_approvers to the escalatees (+ approval.escalated notify), auto_approve/auto_reject finalizes the request and resumes the run, notify posts approval.sla_breached to the pending approvers", + "oracle": "api", + "verify": "for the authored action, cite the concrete effect: reassign → pending_approvers == escalatees; auto_approve → status=approved + run resumed; auto_reject → status=rejected + run resumed; notify → an approval.sla_breached inbox row for each pending approver (escalateRequest, approval-service.ts)", + "evidence": "request/run/notification reads per variant" + }, + { + "clause": "notifySubmitter is honored: with notifySubmitter!==false the original submitter is notified of the SLA breach", + "oracle": "api", + "verify": "the submitter's inbox carries an approval.sla_breached notification naming the escalation action taken", + "evidence": "submitter notification read" + }, + { + "clause": "the escalation config is strict at authoring: an unknown key (or a remappable alias like `timeout`/`sla`) is rejected/normalized at build, so a declared SLA can never silently no-op", + "oracle": "build", + "verify": "the unknown-key scratch flow fails validate with a located error naming the escalation surface (ApprovalEscalationSchema.strict, #4001 — 'until #4001 these were dropped silently')", + "evidence": "build/validate output" + } + ], + "negative": [ + "a request left pending past its sla_due_at with NO escalate row ever written is a FAIL — the #4001 'declared but never fired' shape the strict schema + sweep exist to close", + "a second escalate row on a re-run is a FAIL — escalation is single-shot, guarded by the audit marker", + "an escalation config that parses with an unknown key silently dropped is a FAIL — it means the author's SLA intent was discarded (pre-#4001 behavior)" + ], + "traps": ["seed-data-thin", "stale-dist"], + "source": [ + "packages/spec/src/automation/approval.zod.ts (ApprovalEscalationSchema — enabled/timeoutHours(min 1)/action(reassign|auto_approve|auto_reject|notify)/escalateTo/notifySubmitter, strict, #4001; carried on the approval node as config.escalation)", + "packages/plugins/plugin-approvals/src/approval-service.ts (runEscalations sweep + escalateRequest — audit-first escalate row, per-action effects, notifySubmitter; slaDueAt; ESCALATION_JOB_NAME ADR-0042; this.clock)", + "packages/plugins/plugin-approvals/src/sys-approval-request.object.ts (sla_due_at surfaced on the request)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "initial — pins the ADR-0042 SLA escalation (declared action fires once past timeout + escalate timeline row); blocked on a clock-control timing harness (hour granularity), with the sla_due_at materialization and the strict-schema build clause runnable today", "ref": "claude/platform-test-checklist-ocwugl" } + ] } ] } diff --git a/docs/qa/platform-checklist/areas/attachments-storage.json b/docs/qa/platform-checklist/areas/attachments-storage.json new file mode 100644 index 0000000000..8e503ca21e --- /dev/null +++ b/docs/qa/platform-checklist/areas/attachments-storage.json @@ -0,0 +1,581 @@ +{ + "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md.", + "area": "attachments-storage", + "title": "Attachments & storage — presigned/chunked upload, signed-URL downloads, parent-derived access, sys_file lifecycle", + "items": [ + { + "id": "attachments-storage.presigned-upload-roundtrip", + "title": "Authenticated presigned upload → committed sys_file → signed-URL download round-trip; anonymous upload is 401", + "since": "v15.1", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": ["seeded admin (admin@objectos.ai)", "anonymous (no bearer token)"], + "fixtures": { + "app": "showcase", + "requires": [ + "the storage service booted with the local adapter (stock `objectstack dev` pairing) — routes mount at the default base /api/v1/storage" + ] + }, + "steps": [ + "as admin, POST /api/v1/storage/upload/presigned with { filename: 'qa-roundtrip.png', mimeType: 'image/png', size: , scope: 'attachments' } and capture { uploadUrl, method, fileId, downloadUrl }", + "read the fresh sys_file row (GET /api/v1/data/sys_file/ as admin): status must be 'pending', owner_id must equal the admin's user id (server-stamped from the session, storage-routes.ts)", + "PUT the file bytes to the returned uploadUrl (local driver: PUT /api/v1/storage/_local/raw/:token — an HMAC-token capability URL, followed opaquely exactly as an S3 presigned URL would be)", + "POST /api/v1/storage/upload/complete with { fileId } and re-read the sys_file row: status must now be 'committed'", + "GET /api/v1/storage/files//url with the admin bearer and capture the response envelope", + "GET the returned signed url and compare the served bytes to the uploaded payload", + "GET /api/v1/storage/files/ (no /url suffix) and capture the redirect — the stable browser door 302s to the same short-lived signed URL", + "repeat step 1 with NO Authorization header and capture the refusal" + ], + "acceptance": [ + { + "clause": "presign answers 200 with { uploadUrl, method, fileId, expiresIn, downloadUrl } and persists a status='pending' sys_file whose owner_id is the session user — a client-supplied owner never wins", + "oracle": "api", + "verify": "the presign response fields + a sys_file read showing status 'pending' and owner_id == the admin's sys_user id", + "evidence": "presign response + sys_file read" + }, + { + "clause": "complete flips the sys_file status pending → committed", + "oracle": "api", + "verify": "before/after sys_file reads around POST /upload/complete", + "evidence": "the two reads" + }, + { + "clause": "GET /files/:fileId/url answers the declared envelope { success: true, data: { url } } (the bare { url } retired in #3689) and the url serves back the exact uploaded bytes", + "oracle": "api", + "verify": "envelope shape check + byte-for-byte comparison of the downloaded body against the uploaded payload", + "evidence": "the /url response + a hash of both payloads" + }, + { + "clause": "GET /files/:fileId (browser capability door) 302-redirects to the same signed URL — this is the value objectql stamps into file/image field payloads, so it must work verbatim in an /", + "oracle": "network", + "verify": "the response is a 302 whose Location resolves to the bytes", + "evidence": "the redirect trace" + }, + { + "clause": "anonymous presigned upload is refused 401 AUTH_REQUIRED — the upload session gate is wired, not open-mode", + "oracle": "api", + "verify": "POST /upload/presigned without a bearer returns 401 with code AUTH_REQUIRED; no sys_file row is created for the attempt", + "evidence": "the 401 body + a sys_file count before/after" + } + ], + "negative": [ + "a 2xx on the anonymous presign is a FAIL (it means the deployment is running in the bare-kernel open mode the resolver exists to close) — check the boot log for the one-time '[storage] no session resolver wired' notice before blaming the route", + "a downloadUrl that 404s when followed is the #3641 regression (a minted-but-unmounted URL) — FAIL, not environment" + ], + "traps": ["dispatcher-vs-hono-route"], + "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts" }, + "source": [ + "docs/plans/release-15.1-test-plan.md §A12 / §C1", + "packages/services/service-storage/src/storage-routes.ts (upload session gate #2755, envelope #3689, 302 door)", + "packages/services/service-storage/src/storage-route-ledger.ts (the audited route set at the default base; _local/raw is a server-only capability URL)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item grounded in the storage route ledger + storage-routes.ts source and the #2755 dogfood matrix (upload gate, owner stamping, envelope, 302 door)", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "attachments-storage.download-authz-both-sides", + "title": "Gated downloads: 401 anonymous, 403 parent-invisible, signed URL for the entitled — per gating class (attachments-scope / field-owned / public_read opt-out / ungated)", + "since": "v15.1", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["admin (uploader/owner)", "signed-up member who cannot read the parent record", "anonymous"], + "fixtures": { + "app": "showcase", + "requires": [ + "a committed attachments-scope file attached to a parent record, and a field-owned file (e.g. a receipt uploaded into showcase_invoice_line.receipt — ref_object/ref_id stamped per ADR-0104 D3 wave 2)" + ], + "knownGaps": [ + "stock showcase's only files-enabled object (showcase_project) is public_read_write, so no stock parent is invisible to any member — the 403 deny side is proven by the pinned dogfood fixture (att_secret, private owner-scoped: fixtures/attachments-fixture.ts) or a scratch private parent, not on stock seeds", + "no stock seed mints an acl='public_read' attachments file (the anonymous-embed opt-out) — author one via a system write or accept the unit-test coverage in storage-routes.test.ts for that variant" + ] + }, + "steps": [ + "as admin, upload + attach a file to a parent record (attachments scope) and separately upload a receipt into an invoice line (field-owned; verify ref_object='showcase_invoice_line' and ref_id are stamped on its sys_file row)", + "GET /api/v1/storage/files//url with NO auth for each gated file and capture the refusals", + "GET the same routes as a member who cannot read the parent record (private parent per the fixture note) and capture the refusals — note the two DISTINCT deny codes", + "GET the same routes as the uploader/admin and follow the signed URL", + "compare TTLs: the gated grant uses the short downloadTtl (default 300s), not the 3600s presignedTtl — read expiry material from the minted URL/descriptor where the adapter exposes it", + "flip one file to acl='public_read' (system write) and GET /files/ anonymously — the opt-out must restore the stable anonymous capability URL", + "in the browser, open the parent record's RecordAttachmentsPanel as the denied member and capture the surfaced copy" + ], + "acceptance": [ + { + "clause": "anonymous download of a gated file (attachments-scope OR field-owned) is 401 AUTH_REQUIRED", + "oracle": "api", + "verify": "both gated classes answer 401 with code AUTH_REQUIRED when no session resolves", + "evidence": "the two 401 bodies" + }, + { + "clause": "an authenticated member without read access to the parent is 403, with the class-specific code: ATTACHMENT_DOWNLOAD_DENIED for attachments-scope, FILE_DOWNLOAD_DENIED for field-owned", + "oracle": "api", + "verify": "the 403 bodies carry exactly those codes (storage-routes.ts authorizeDownload); a failed authz check must deny, never fall open", + "evidence": "the two 403 bodies" + }, + { + "clause": "the entitled caller (parent-visible member, or the uploader/owner who may ALWAYS download) receives a working short-lived signed URL", + "oracle": "api", + "verify": "200 { success: true, data: { url } } and the url serves the bytes; uploader bypass verified by downloading as the file's owner_id user against a parent they cannot otherwise read", + "evidence": "the grant + downloaded bytes" + }, + { + "clause": "acl='public_read' opts a file back out to the stable anonymous capability URL — the explicit declaration for embedding, which cannot carry a bearer token", + "oracle": "api", + "verify": "after setting acl public_read, the anonymous GET /files/:fileId 302s to bytes instead of 401ing", + "evidence": "before/after anonymous responses" + }, + { + "clause": "the browser surface renders the denial as friendly copy, not a raw error dump (objectui#2532) — the RecordAttachmentsPanel maps the fail-closed 40x codes to readable text", + "oracle": "screenshot", + "verify": "the denied member's panel shows the mapped copy (RecordAttachmentsPanel.tsx friendlyError reads both the enveloped error.code and the legacy top-level code)", + "evidence": "panel screenshot" + } + ], + "negative": [ + "a gated file downloadable anonymously (silent success) is a FAIL — UI absence of a download button is a client courtesy; the route is the authority (ADR-0057 D10)", + "a deny that surfaces in the panel as 'Download failed (403)' instead of the mapped copy means the error-envelope dialect broke (#3689 note in the panel) — file it against objectui, not storage" + ], + "traps": ["wrong-persona", "dispatcher-vs-hono-route", "stale-console-bundle"], + "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts" }, + "source": [ + "docs/plans/release-15.1-test-plan.md §A12 / §C1 (#2755/#2970)", + "packages/services/service-storage/src/storage-routes.ts (authorizeDownload: gating classes, verdict→status mapping, downloadTtl vs presignedTtl)", + "packages/services/service-storage/src/storage-service-plugin.ts (buildFileReadAuthorizer: owner bypass, field-owned single-parent read, fail-closed delegate)", + "objectui packages/app-shell/src/views/RecordAttachmentsPanel.tsx (friendly denial copy, objectui#2532)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item enumerating the download gating classes straight from authorizeDownload's source (attachments-scope / field-owned / public_read opt-out) with the exact deny codes and TTL contrast", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "attachments-storage.read-inherits-parent-rls", + "title": "sys_attachment reads inherit parent visibility: a restricted member sees neither rows nor counts for invisible parents", + "since": "v15.1", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": ["member who can read the parent record", "member who cannot read the parent record"], + "fixtures": { + "app": "showcase", + "requires": [ + "attachments on a parent record that is INVISIBLE to one persona and visible to the other" + ], + "knownGaps": [ + "stock showcase's files-enabled parent (showcase_project) is public_read_write — every member sees every project, so the invisible-parent case is not demonstrable on stock seeds; the pinned dogfood matrix proves it on its own private fixture (att_secret). A stock-seed demonstration needs a private files-enabled object added to the showcase" + ] + }, + "steps": [ + "seed the split: attach files to a record the restricted member cannot read (private, owned by admin) and to one they can", + "as the restricted member, GET /api/v1/data/sys_attachment (list scoped by parent_object + parent_id of the invisible record) and record rows AND total", + "as the same member, GET the invisible parent's attachment by id directly", + "run the same reads as the entitled member", + "as the restricted member, run an unscoped sys_attachment list and record which rows appear", + "run a count/aggregate over sys_attachment as both personas and compare totals" + ], + "acceptance": [ + { + "clause": "the restricted member's list returns zero rows for the invisible parent — attachment metadata (file_name, size, parent_id) never leaks", + "oracle": "api", + "verify": "the scoped list is empty for the restricted member and non-empty for the entitled one", + "evidence": "both list responses" + }, + { + "clause": "the COUNT is filtered identically to the rows — the visibility filter is a data middleware precisely so list `total` (engine.count, not the find path) cannot leak the true row count", + "oracle": "api", + "verify": "total/count for the restricted member excludes invisible-parent rows; the entitled member's count includes them", + "evidence": "the paired counts" + }, + { + "clause": "a direct by-id read of an invisible parent's attachment resolves to nothing for the restricted member", + "oracle": "api", + "verify": "findOne/GET by id returns not-found/empty, not the row", + "evidence": "the by-id response" + }, + { + "clause": "the filter fails CLOSED: a filter-compute failure or a pre-scan past the 2000-candidate cap excludes rows rather than leaking them, and the cap logs a warning naming the fail-closed truncation", + "oracle": "log", + "verify": "on a very broad unscoped list, either all rows resolve visibly or the '[storage] attachment read visibility' warning appears — silence plus leaked rows is the failure", + "evidence": "log excerpt for the broad-read case" + } + ], + "negative": [ + "rows visible to the restricted member whose parent they cannot read is the #2970 info leak this item exists for — FAIL", + "rows filtered but `total` counting the raw table is equally a FAIL (the middleware-not-hook design note in attachment-access-hooks.ts exists because a find-hook leaves count() unfiltered)" + ], + "traps": ["wrong-persona"], + "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts" }, + "source": [ + "docs/plans/release-15.1-test-plan.md §C3 (#2970)", + "packages/services/service-storage/src/attachment-access-hooks.ts (installAttachmentReadVisibility: middleware over find/findOne/count/aggregate, deny-all sentinel, READ_SCAN_LIMIT fail-closed cap)", + "packages/services/service-storage/src/attachment-read-visibility.test.ts" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item grounded in the read-visibility middleware source (count-parity rationale, fail-closed sentinel and scan cap) and the dogfood matrix clause (c)", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "attachments-storage.attach-requires-parent-edit", + "title": "Attaching requires EDIT on the parent record; deleting requires uploader-or-parent-editor; unscoped multi-delete is refused outright", + "since": "v15.1", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": ["member with EDIT on the parent", "member with READ but not EDIT on the parent", "member who uploaded the attachment", "member who is neither uploader nor parent editor"], + "fixtures": { + "app": "showcase", + "requires": [ + "a files-enabled parent readable-but-not-editable by one persona (the dogfood matrix builds this with its own fixture; on stock showcase, projects are public_read_write so any member can edit — the read-not-edit split needs the fixture or a sharing tweak)", + "plugin-sharing present (canEdit is the authority; without it the gate degrades to parent READ visibility by design)" + ] + }, + "steps": [ + "as the read-only member, POST /api/v1/data/sys_attachment { parent_object, parent_id, file_id, file_name, mime_type, size } against the readable-but-not-editable record and capture the refusal", + "as the same member, list that parent's attachments — reading must still work (read inherits parent READ, attach requires parent EDIT: two different gates)", + "as a parent editor, POST the same attach payload with a spoofed uploaded_by of another user and read the row back", + "as a member who is neither the uploader nor a parent editor, DELETE the attachment and capture the refusal", + "as the uploader, DELETE their own attachment on a parent they cannot edit — the uploader may always detach", + "issue a DELETE against /api/v1/data/sys_attachment with NO id and NO where predicate and capture the refusal" + ], + "acceptance": [ + { + "clause": "attach without parent EDIT is 403 ATTACHMENT_PARENT_ACCESS — Salesforce parity (#2970 item 3): canEdit on the parent, not mere read", + "oracle": "api", + "verify": "the read-only member's insert is refused with that code, while the same member's LIST of the parent's attachments succeeds", + "evidence": "the 403 + the successful list" + }, + { + "clause": "uploaded_by is server-stamped from the session — a spoofed value never wins", + "oracle": "api", + "verify": "the created row's uploaded_by equals the caller's user id, not the spoofed one", + "evidence": "the row read" + }, + { + "clause": "delete is gated on uploader-or-parent-editor: the outsider gets 403 ATTACHMENT_DELETE_DENIED; the uploader succeeds even without parent edit", + "oracle": "api", + "verify": "the two delete attempts split exactly that way; a multi-delete requires EVERY matched row to pass", + "evidence": "both delete responses" + }, + { + "clause": "an unscoped multi-delete (no id AND no where) is refused outright (#4757) — 'nothing was ever queried' must not read as 'nothing to authorize'", + "oracle": "api", + "verify": "the predicate-less delete returns 403 ATTACHMENT_DELETE_DENIED with the refusing-unscoped message; the table row count is unchanged", + "evidence": "the refusal + before/after counts" + }, + { + "clause": "attaching to an object without enable.files is 403 FILES_DISABLED (the #2727 opt-in gate, enforced by plugin-audit alongside these hooks)", + "oracle": "api", + "verify": "an attach targeting e.g. showcase_account (no enable.files) is refused with FILES_DISABLED", + "evidence": "the refusal" + } + ], + "negative": [ + "a successful attach by the read-only member is a FAIL even if the UI hides the upload control — the hook, not the panel, is the boundary", + "a rejected write that still created the sys_attachment row (verify by re-listing) is a FAIL — the rejection must be authoritative, not cosmetic" + ], + "traps": ["wrong-persona"], + "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts" }, + "source": [ + "docs/plans/release-15.1-test-plan.md §A12 (attach 需 parent EDIT)", + "packages/services/service-storage/src/attachment-access-hooks.ts (beforeInsert canEdit gate + uploaded_by stamping; beforeDelete uploader-or-editor + #4757 unscoped refusal, MULTI_DELETE_AUTH_LIMIT fail-closed)", + "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts (items 3, a, f; FILES_DISABLED)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item from the access-hook source: EDIT-not-read attach gate, server stamping, delete authorization matrix and the #4757 unscoped-delete refusal, each with its exact deny code", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "attachments-storage.sys-file-status-pipeline", + "title": "sys_file status pipeline: pending → committed → deleted (tombstone) with un-tombstone on re-attach; shared files never tombstone early", + "since": "v15.1", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "api", + "personas": ["seeded admin (admin@objectos.ai)"], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_project (enable.files) with seeded projects 'Website Relaunch' and 'Data Platform' as the two attach targets" + ] + }, + "steps": [ + "presign an attachments-scope upload and read the sys_file row: status 'pending'", + "attempt GET /api/v1/storage/files//url while still pending and capture the refusal (downloads only serve committed files)", + "complete the upload; re-read: status 'committed'", + "attach the file to 'Website Relaunch' AND 'Data Platform' (two sys_attachment join rows over ONE file — the Salesforce ContentDocumentLink share pattern)", + "delete the 'Data Platform' join row and re-read sys_file: still 'committed' (a remaining reference blocks the tombstone)", + "delete the LAST join row and re-read: status 'deleted' with deleted_at set (the tombstone)", + "re-attach the same file_id to 'Website Relaunch' within the grace window and re-read: status back to 'committed', deleted_at null", + "verify a NON-attachments-scope file (e.g. an invoice-line receipt, scope from the field-upload path) is never tombstoned by these join-row hooks" + ], + "acceptance": [ + { + "clause": "each variant of the status enum (pending / committed / deleted — the full option set declared on sys_file.status) is reached through its real transition, verified by API reads, never inferred", + "oracle": "api", + "verify": "the sequence of sys_file reads shows pending→committed on complete, committed→deleted on last-reference delete, deleted→committed on re-attach", + "evidence": "the read sequence, one per transition" + }, + { + "clause": "a pending (never-completed) file is not downloadable: the download routes answer 404 FILE_NOT_FOUND for status != committed", + "oracle": "api", + "verify": "the /url GET during the pending window returns 404 with that code", + "evidence": "the 404 body" + }, + { + "clause": "one file shared by two join rows survives losing one of them — deleting an attachment deletes only the join row; the tombstone fires only when the LAST reference goes", + "oracle": "api", + "verify": "sys_file still committed after the first join-row delete; deleted only after the second", + "evidence": "the two post-delete reads" + }, + { + "clause": "re-attaching before the 30d grace window un-tombstones (status committed, deleted_at cleared) — the tombstone is recoverable state, not a delete", + "oracle": "api", + "verify": "post-re-attach read shows the revived row", + "evidence": "the read" + }, + { + "clause": "only scope='attachments' committed files tombstone via these hooks — field-owned scopes have their own release seam (file-reference-lifecycle.ts) and must not be touched by join-row counting", + "oracle": "api", + "verify": "a non-attachments-scope file's status is unchanged by sys_attachment deletes", + "evidence": "before/after reads of the field-file row" + } + ], + "negative": [ + "a tombstone firing while a reference remains (the shared-file case) is a FAIL — it is the exact naive cascade the join-row model exists to prevent", + "lifecycle bookkeeping blocking or failing the user's delete is a FAIL: the hooks are declared best-effort (fail toward retention, log-only)" + ], + "variants": ["pending", "committed", "deleted"], + "traps": ["wrong-panel"], + "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts" }, + "source": [ + "packages/services/service-storage/src/objects/system-file.object.ts (status enum + lifecycle declaration; the variants list is that enum)", + "packages/services/service-storage/src/attachment-lifecycle.ts (tombstone/un-tombstone hooks, last-reference rule, attachments-scope discriminator)", + "#3358 §7 (sys_file detail page + status pipeline)", + "docs/plans/release-15.1-test-plan.md §C4 (#2755)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: the full status pipeline as a variants matrix over the sys_file.status enum, with the shared-file and re-attach transitions from the lifecycle-hook source", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "attachments-storage.orphan-tombstone-reap", + "title": "The platform lifecycle sweep reaps expired tombstones and abandoned pending uploads WITH byte reclaim; sweep-time re-verification vetoes rather than losing data", + "since": "v15.1", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "api", + "personas": ["admin (setup access, drives the sweep)"], + "fixtures": { + "app": "showcase", + "requires": [ + "the ability to backdate deleted_at/created timestamps past the TTLs (system writes) so the sweep selects candidates without waiting 30d/7d — the pinned dogfood suite does exactly this" + ] + }, + "steps": [ + "run the pinned dogfood lifecycle suite first (RUNNER rule 6 — do not hand re-prove what it pins) and capture its output", + "for the hand-driven spot check: tombstone a file (last join-row delete), backdate deleted_at past 30d, trigger the LifecycleService sweep, then verify the sys_file row AND its storage bytes are gone", + "create a pending upload, backdate past the 7d retention, sweep, verify row + best-effort bytes gone", + "create a tombstone, then re-add a sys_attachment reference BEHIND the hooks (system write), backdate, sweep — verify the row is un-tombstoned (committed, deleted_at null), not reaped", + "verify fresh tombstones (inside the window), committed rows and NULL-deleted_at rows all survive the sweep untouched" + ], + "acceptance": [ + { + "clause": "each reap-guard verdict variant behaves per its declared contract: pending → bytes best-effort deleted + row confirmed; deleted with zero references and no field owner → bytes reclaimed BEFORE the row delete; deleted-but-regained-reference → un-tombstoned and vetoed; non-attachments-scope tombstone with the ADR-0104 migration gate closed → kept (vetoed, still tombstoned); byte-delete failure → vetoed and retried next sweep", + "oracle": "test", + "verify": "the dogfood lifecycle describe block ('sys_file orphan lifecycle (ADR-0057 reap guard)') passes; its cases map one-to-one onto these variants", + "evidence": "test run output" + }, + { + "clause": "bytes are reclaimed before the row delete — the row is the only pointer to the bytes, so dropping it first would leak them forever", + "oracle": "api", + "verify": "after the sweep, the storage backend no longer holds the reaped key AND the sys_file row is gone; a failed byte delete leaves the row for retry (check the warn log)", + "evidence": "backend listing + row read + log excerpt" + }, + { + "clause": "sweep-time re-verification is real: a reference regained behind the hooks' back (hook bypass, restore) un-tombstones instead of reaping", + "oracle": "api", + "verify": "the re-referenced file survives the sweep as committed", + "evidence": "post-sweep read" + }, + { + "clause": "committed rows are immortal: nothing with status='committed' or NULL deleted_at is ever a candidate", + "oracle": "api", + "verify": "control rows are unchanged after the sweep", + "evidence": "control-row reads" + } + ], + "negative": [ + "a reap that deletes the row but leaves the bytes (or vice versa without a veto) is a FAIL — the guard's ordering contract exists precisely for this", + "the sweep reaping a fresh (in-window) tombstone is a FAIL against the TTL declaration on system-file.object.ts" + ], + "variants": [ + "pending (7d retention, bytes best-effort)", + "deleted + zero refs (30d TTL, bytes reclaimed then row)", + "deleted + regained reference (un-tombstone, veto)", + "deleted + field-file lineage, migration gate closed (kept, veto without un-tombstone)", + "byte-delete failure (veto, retry next sweep)" + ], + "traps": ["stale-dist"], + "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts" }, + "source": [ + "packages/services/service-storage/src/attachment-lifecycle.ts (createSysFileReapGuard — the verdict variants are its documented branch set, incl. the ADR-0104 isCollectionOpen gate re-read each sweep)", + "packages/services/service-storage/src/objects/system-file.object.ts (lifecycle: ttl deleted_at+30d, retention 7d onlyWhen pending)", + "docs/plans/release-15.1-test-plan.md §C4 (#2755/#2970)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: reap-guard verdict matrix enumerated from createSysFileReapGuard's source, pinned to the dogfood lifecycle suite; TTL/retention figures from the sys_file lifecycle declaration", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "attachments-storage.upload-session-abort", + "title": "Chunked uploads are resumable; abandoned sessions are reaped and their backend multipart uploads ABORTED before the row (the only pointer) is deleted", + "since": "v15.1", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "api", + "personas": ["seeded admin (admin@objectos.ai)"], + "fixtures": { + "app": "showcase", + "requires": [ + "the four chunked routes at the default base: POST /upload/chunked, PUT /upload/chunked/:uploadId/chunk/:chunkIndex, POST /upload/chunked/:uploadId/complete, GET /upload/chunked/:uploadId/progress" + ], + "knownGaps": [ + "the billable-stranded-parts consequence (S3 keeps initiated-but-uncompleted multipart parts invisible until AbortMultipartUpload) is only observable against a real S3 backend — stock local-adapter runs prove the session-row lifecycle and that the guard invokes abortChunkedUpload; the S3 key re-seeding path (setUploadKey on a cold sweep) is pinned by unit tests in attachment-lifecycle.test.ts, not demonstrable on local" + ] + }, + "steps": [ + "POST /api/v1/storage/upload/chunked { filename, mimeType, size, chunkSize } and capture { uploadId, fileId }", + "PUT one chunk, then GET /upload/chunked//progress and record uploadedChunks/percentComplete/status ('in_progress') — progress is the first step of the SDK's resumeUpload", + "complete a full session on a second upload and verify its sys_upload_session row reaches status 'completed'", + "abandon the first session mid-flight; backdate its expires_at past the 1d TTL (system write) and trigger the lifecycle sweep", + "verify the abandoned session's row is reaped AND the storage adapter's abortChunkedUpload was invoked for its backend_upload_id (dogfood suite instruments this)", + "verify the completed session's row is reaped by the 7d terminal-status retention WITHOUT an abort attempt (an abort on a finalized multipart would NoSuchUpload-error and wedge the reap)", + "simulate an abort failure (test seam) and verify the row is VETOED — kept so backend_upload_id survives for the retry" + ], + "acceptance": [ + { + "clause": "the chunked round-trip works over the real routes: init → chunk PUTs → progress read → complete, with the session row tracking uploadedChunks and status through the declared enum", + "oracle": "api", + "verify": "progress responses and sys_upload_session reads at each stage match the declared shape", + "evidence": "the progress/read sequence" + }, + { + "clause": "an abandoned in_progress session with a backend_upload_id is reaped only AFTER a successful backend multipart abort — parts never stranded with their only pointer gone", + "oracle": "test", + "verify": "the dogfood case '(item 4 + multipart-abort guard) an abandoned chunked upload is reaped AND its uploaded parts are aborted' passes", + "evidence": "test run output" + }, + { + "clause": "completed sessions (and sessions with no backend_upload_id, and adapters without abortChunkedUpload) reap WITHOUT an abort call", + "oracle": "test", + "verify": "the guard confirms those rows directly per its documented branch set", + "evidence": "test output / instrumented call log" + }, + { + "clause": "an abort failure vetoes: the row survives the sweep and is retried, with the '[storage] reap guard: multipart abort failed' warning logged", + "oracle": "log", + "verify": "the veto case keeps the row and logs the retry warning", + "evidence": "log excerpt + row read" + }, + { + "clause": "every sys_upload_session status enum variant (in_progress / completing / completed / failed / expired) is reachable and terminal ones fall under the 7d retention backstop", + "oracle": "api", + "verify": "status reads across the scenarios cover the enum; retention onlyWhen matches {status: {$in: [completed, failed, expired]}}", + "evidence": "per-variant status reads" + } + ], + "negative": [ + "a sweep that deletes the session row while the backend abort failed (silent success) is a FAIL — the row's backend_upload_id is the sole pointer to the leaked multipart", + "an abort attempted against a COMPLETED session is a FAIL (it would NoSuchUpload-error and wedge the reap)" + ], + "variants": ["in_progress", "completing", "completed", "failed", "expired"], + "traps": ["stale-dist"], + "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts" }, + "source": [ + "packages/services/service-storage/src/attachment-lifecycle.ts (createUploadSessionReapGuard — abort-before-reap contract, completed/no-backend confirm branches, veto-on-failure)", + "packages/services/service-storage/src/objects/system-upload-session.object.ts (status enum = the variants list; ttl expires_at+1d, retention 7d terminal statuses)", + "packages/services/service-storage/src/storage-route-ledger.ts (upload-chunked family)", + "docs/plans/release-15.1-test-plan.md §C4 (#2970 item 4)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: chunked-session lifecycle + multipart-abort guard from the reap-guard source, variants pinned to the sys_upload_session status enum; S3-only consequences honestly recorded as a known gap", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "attachments-storage.inline-grid-receipt-cells", + "title": "Per-line Receipt upload cells in the invoice inline grid: auto-derived file column, real upload control, resolved file object in the atomic batch", + "since": "v15.1", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": ["seeded admin (admin@objectos.ai)"], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_invoice_line.receipt (Field.file) and the seeded product 'Widget A' (sku WIDGET-A) for the line's product pick", + "the storage service live (the upload rides the console's UploadProvider adapter into /api/v1/storage)" + ] + }, + "steps": [ + "open /apps/showcase_app/showcase_invoice, click New, and wait for the Line Items grid's real headers (screenshot first)", + "verify the Receipt column auto-derived into the grid (no columns config authors it — file fields must not be dropped from auto-columns)", + "verify the Receipt cell is a genuine input[type=file] upload control and NOT a text input", + "materialize a row: pick 'Widget A' in the product lookup, set Qty 1", + "upload a small PNG into the row's Receipt cell and wait for the file chip carrying the file name", + "fill the header (name INV-QA-, pick an account, status draft) and submit while capturing the POST /api/v1/batch request", + "inspect the batch's showcase_invoice_line operation: the receipt value must be a RESOLVED stored-file object, and its url an absolute http(s) URL", + "re-open the created invoice and confirm the line's receipt renders as a chip/thumbnail, not a text value" + ], + "acceptance": [ + { + "clause": "the Receipt column auto-derives from the data model into the inline grid", + "oracle": "dom", + "verify": "after the screenshot confirms render, the grid header row contains exactly one 'Receipt' th", + "evidence": "screenshot + header DOM excerpt" + }, + { + "clause": "the cell is a real upload control — input[type=file] present, text input for Receipt absent (the objectui#2360 degraded-cell regression)", + "oracle": "dom", + "verify": "input[type=file] count >= 1 inside the grid; input[type=text][aria-label=Receipt] count == 0", + "evidence": "DOM assertion output" + }, + { + "clause": "picking a file uploads through the storage service and shows a removable chip with the file name before submit", + "oracle": "network", + "verify": "the upload requests hit /api/v1/storage/* and the chip appears with the picked name", + "evidence": "upload trace + chip screenshot" + }, + { + "clause": "the atomic /api/v1/batch carries the line with receipt as a resolved stored-file object (absolute url), not a blob or text placeholder — and the create round-trips", + "oracle": "network", + "verify": "the captured batch's showcase_invoice_line operation has receipt.name (or original_name) containing the picked filename and an http(s) url; the batch answers success", + "evidence": "the batch payload + response" + } + ], + "negative": [ + "a text input where the upload cell should be is the exact #2360 failure — FAIL", + "a batch that persists a data:/blob: placeholder instead of a stored-file reference is a FAIL even if the grid looked right (ADR-0104: an inline blob is not a managed file)" + ], + "traps": ["automation-input", "hydration-race", "stale-console-bundle"], + "automated": { "kind": "e2e", "ref": "objectui: e2e/live/grid-file-upload.spec.ts" }, + "source": [ + "docs/plans/release-15.1-test-plan.md §C2 (#3051 + objectui#2585)", + "objectui e2e/live/grid-file-upload.spec.ts (the live pin: auto-column, input-not-text, chip, batch payload shape)", + "examples/app-showcase/src/data/objects/invoice.object.ts (showcase_invoice_line.receipt = Field.file, objectui#2360 note)", + "ADR-0059 — the FORM-side Confirm-disabled-while-uploading guard is records-forms.upload-guard-blocks-confirm; this item owns the storage/persistence side. Cross-reference, do not duplicate" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item transcribed from the live e2e pin (grid-file-upload.spec.ts) with seeded names verified (Widget A / showcase_invoice_line.receipt); ADR-0059 form-side guard cross-referenced to records-forms instead of duplicated", "ref": "claude/platform-test-checklist-ocwugl" } + ] + } + ] +} diff --git a/docs/qa/platform-checklist/areas/automation.json b/docs/qa/platform-checklist/areas/automation.json index 1e687e318d..f0d5a7b640 100644 --- a/docs/qa/platform-checklist/areas/automation.json +++ b/docs/qa/platform-checklist/areas/automation.json @@ -465,7 +465,7 @@ "title": "Screen flow round-trip: action trigger → paused run → rendered dialog → resume with inputs → persisted write", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "browser", "personas": ["admin"], @@ -482,7 +482,9 @@ "the console renders the screen as a dialog: screenshot it (heading 'New Assignee')", "fill #ff-new_assignee with a unique value and Submit; record the resume POST body", "re-read the task over /api/v1/data/showcase_task/:id — the assignee must equal the submitted value", - "negative probe: trigger the action again on another row, then POST /runs/:runId/resume directly with an EMPTY inputs bag" + "negative probe: trigger the action again on another row, then POST /runs/:runId/resume directly with an EMPTY inputs bag", + "cancel-mid-flow probe: trigger the action on a fresh row to open the FlowRunner dialog, note its runId (GET /runs — the newest paused run), then DISMISS the dialog via its Cancel button (or the dialog close) WITHOUT submitting; do NOT POST resume", + "after the cancel: GET /api/v1/automation/showcase_reassign_wizard/runs/:runId for that run, then GET /runs/:runId/screen and POST /runs/:runId/resume with valid inputs to prove it is still resumable, and re-read the target task" ], "acceptance": [ { @@ -520,21 +522,30 @@ "oracle": "api", "verify": "the empty-inputs resume answers 400 (screen-resume validation); the run re-reads as paused, and a later valid resume still works", "evidence": "rejected resume response + run reads" + }, + { + "clause": "cancelling the screen dialog mid-flow closes the dialog but does NOT consume the durable suspension: the run is parked (still paused at 'collect'), not orphaned or cancelled, and remains resumable", + "oracle": "api", + "verify": "FlowRunner's Cancel/close only calls onClose (setScreenFlow(null)) — it POSTs nothing, so GET /runs/:runId re-reads status=paused at node 'collect'; a follow-up GET /runs/:runId/screen still serves the contract and a POST /runs/:runId/resume with valid inputs completes it (status=completed) and the downstream update_record lands — proving the parked run was resumable, never an orphan", + "evidence": "post-cancel run read (paused) + the later successful screen read/resume + task read" } ], "negative": [ - "a resume with the required new_assignee absent that answers 2xx or completes the run is a FAIL — the screen contract's required flag must be enforced server-side, not only by the dialog" + "a resume with the required new_assignee absent that answers 2xx or completes the run is a FAIL — the screen contract's required flag must be enforced server-side, not only by the dialog", + "a cancel that leaves the run status=cancelled/completed, deletes the paused row, or silently fires the downstream update_record is a FAIL — dismissing the dialog must not consume the durable suspension (the run must stay resumable); equally, a cancel that makes the run unresumable (a later valid resume 404s 'no suspended run') is a FAIL" ], "traps": ["automation-input", "hydration-race"], "automated": { "kind": "e2e", "ref": "objectui e2e/live/screen-flow.spec.ts" }, "source": [ "objectui e2e/live/screen-flow.spec.ts (framework#3528 — the trigger → dialog → resume → refresh seam)", + "objectui packages/app-shell/src/views/FlowRunner.tsx (onClose = dismiss without POSTing resume; the durable suspension is untouched, so the paused run stays resumable)", "examples/app-showcase/src/automation/flows/index.ts (ReassignWizardFlow)", "packages/services/service-automation/src/builtin/screen-resume-validation.test.ts + src/screen-input-contract.ts (400 on missing required inputs)", "packages/runtime/src/route-ledger.ts (resume + getScreen routes)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — promotes the objectui live e2e's seam to a ledger item and adds the server-side required-input negative", "ref": "claude/platform-test-checklist-ocwugl" } + { "revision": 1, "date": "2026-08-07", "change": "initial — promotes the objectui live e2e's seam to a ledger item and adds the server-side required-input negative", "ref": "claude/platform-test-checklist-ocwugl" }, + { "revision": 2, "date": "2026-08-08", "change": "added the FlowRunner cancel-mid-flow clause — dismissing the screen dialog parks the run (still paused, resumable) rather than orphaning/cancelling it; API run state is the oracle", "ref": "claude/platform-test-checklist-ocwugl" } ] }, { @@ -677,6 +688,149 @@ "history": [ { "revision": 1, "date": "2026-08-07", "change": "initial — connector dispatch matrix over the plugin / declarative-rest / declarative-MCP kinds the showcase seeds, with the unregistered-id negative", "ref": "claude/platform-test-checklist-ocwugl" } ] + }, + { + "id": "automation.flow-runs-page-test-trigger", + "title": "The developer Flow Runs page triggers a run with typed inputs and drives a screen flow's pause to completion (no orphaned paused rows)", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_batch_reminders (examples/app-showcase/src/automation/flows/index.ts BatchRemindersFlow) — an autolaunched loop flow declaring one INPUT variable `tasks` (type list, isInput:true), so the Test Run panel renders a JSON textarea for it", + "showcase_reassign_wizard (ReassignWizardFlow, type screen) declaring input variables recordId + new_assignee (text) — executing it from this page returns {status:'paused', runId, screen} and the page hands the pause to FlowRunner" + ] + }, + "steps": [ + "boot showcase isolated (dogfood §0); sign in as the dev admin", + "open the developer Flow Runs PAGE (apps/console FlowRunsPage) at /apps/showcase_app/component/developer/flow-runs (console route developer/flow-runs) — NOT the Studio flow-designer Runs preview", + "typed-input run: select 'Batch Task Reminders (Loop)' (showcase_batch_reminders); the Test Run panel shows the `tasks` input as a JSON `list` textarea; paste three task-shaped objects [{id,title,owner}×3] and click 'Run Flow'", + "record the trigger POST (client.automation.execute → POST /api/v1/automation/showcase_batch_reminders/trigger) and screenshot the Result envelope; confirm a fresh row appeared in the Recent Runs panel (client.automation.listRuns → GET /automation/showcase_batch_reminders/runs)", + "GET /api/v1/automation/showcase_batch_reminders/runs and match the newest run id/status against the panel row", + "screen-flow run: select 'showcase_reassign_wizard'; fill recordId (a real showcase_task id) + new_assignee, click 'Run Flow' — the Result reads 'Waiting for input' (status=paused) and the FlowRunner dialog opens on THIS page", + "screenshot the FlowRunner dialog; fill #ff-new_assignee and Submit; record the resume POST (POST /runs/:runId/resume)", + "GET /api/v1/automation/showcase_reassign_wizard/runs — the driven run reads status=completed; count the paused rows for this flow before vs after and assert the count did not grow (the framework#3528 orphaned-paused regression)", + "re-read the target task over /api/v1/data/showcase_task/:id — its assignee equals the submitted value", + "negative probe: run the screen flow again but DISMISS the FlowRunner without submitting; confirm the page offers a 'Continue run' affordance (the durable pause is reopenable) rather than losing the run" + ], + "acceptance": [ + { + "clause": "a typed-input flow triggers from the page: the `tasks` list input is rendered and editable, Run POSTs the trigger route, and the Result envelope reports a completed run", + "oracle": "network", + "verify": "capture POST /api/v1/automation/showcase_batch_reminders/trigger carrying {params:{tasks:[…3…]}}; the Result JsonBlock shows the run completed (or the loop body executed 3×) — not an error envelope", + "evidence": "trigger request/response + Result screenshot" + }, + { + "clause": "the triggered run appears as a history row in the Recent Runs panel and that row matches the API", + "oracle": "api", + "verify": "GET /automation/showcase_batch_reminders/runs returns a newest run whose id + status equal the panel's freshest row (a row the panel invented that the API does not list is a rendering fault; a run the API lists that the panel omits is a refresh fault)", + "evidence": "runs API read + Recent Runs screenshot side by side" + }, + { + "clause": "a SCREEN flow executed from this page pauses and the pause is handed to FlowRunner — the page opens the interactive dialog instead of dumping the {status:'paused'} envelope and stopping", + "oracle": "screenshot", + "verify": "after Run, the Result reads 'Waiting for input' and the FlowRunner dialog renders on the page (the framework#3528 fix: FlowTestRunner sets screenFlow when res.status==='paused' && res.screen && res.runId)", + "evidence": "dialog screenshot with the page behind it" + }, + { + "clause": "driving the FlowRunner dialog to Submit resumes and completes the run", + "oracle": "network", + "verify": "Submit POSTs /api/v1/automation/showcase_reassign_wizard/runs/:runId/resume with the collected inputs; the run re-reads status=completed and the downstream update_record landed on the task", + "evidence": "resume request body + run read + task read" + }, + { + "clause": "no orphaned paused row: after the screen flow is driven to completion, the run reads completed and the flow's paused-row count did not grow — the exact framework#3528 regression the page fix closes", + "oracle": "api", + "verify": "GET /automation/showcase_reassign_wizard/runs before and after: the run that was paused is now completed and the number of runs left in status=paused is unchanged (each test run of a screen flow used to strand a paused row)", + "evidence": "before/after runs reads with the paused-row tally" + } + ], + "negative": [ + "a screen-flow test run that dumps the {status:'paused'} JSON envelope and stops — no dialog, no way to finish it — leaving a paused run stranded is a FAIL: that is the framework#3528 regression this page's FlowRunner hand-off exists to prevent", + "citing the Studio flow-designer Runs preview (metadata-admin FlowRunsPanel) as this item's surface is a wrong-panel FAIL — this item is the developer:flow-runs PAGE (trigger + inline history), which renders steps FLAT; the nested step-tree lives in automation.flow-run-step-nesting" + ], + "traps": ["wrong-panel", "hydration-race", "automation-input"], + "source": [ + "objectui apps/console/src/pages/developer/FlowRunsPage.tsx (FlowTestRunner + inline FlowRunsPanel; framework#3528 — hands a paused screen run to FlowRunner so it no longer orphans a paused row)", + "objectui packages/app-shell/src/views/FlowRunner.tsx (the shared screen runner reused here)", + "objectui apps/console/src/AppContent.tsx (Route path 'developer/flow-runs') + registerDeveloperComponents.tsx (ref 'developer:flow-runs')", + "packages/runtime/src/route-ledger.ts (POST /automation/:name/trigger, GET /automation/:name/runs, POST /automation/:name/runs/:runId/resume)", + "examples/app-showcase/src/automation/flows/index.ts (BatchRemindersFlow tasks input, ReassignWizardFlow screen)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "initial — pins the developer Flow Runs page's typed-input trigger + the framework#3528 screen-flow hand-off (paused run driven to completion, no orphaned row); distinct from flow-run-step-nesting which reads the designer's Runs preview", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "automation.flow-toggle-kill-switch", + "title": "Toggling a record-change flow OFF is a runtime kill switch — the mutation that fired it produces no new run; ON restores firing", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "api", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_urgent_task_alert (examples/app-showcase/src/automation/flows/index.ts UrgentTaskAlertFlow) — the only type:'record_change' flow, bound via record-after-write and firing on a showcase_task created with priority='urgent' (previous==null, #3427)" + ] + }, + "steps": [ + "boot showcase isolated (dogfood §0); sign in as the dev admin", + "baseline: POST /api/v1/data/showcase_task with priority='urgent'; GET /api/v1/automation/showcase_urgent_task_alert/runs and record the run count (one new run for this create)", + "read the pre-toggle runtime state: GET /api/v1/automation/_status (getFlowRuntimeStates) — showcase_urgent_task_alert enabled=true, bound=true", + "toggle OFF: POST /api/v1/automation/showcase_urgent_task_alert/toggle with {enabled:false} (client.automation.toggle → engine.toggleFlow → deactivateFlowTrigger)", + "re-read /_status: enabled=false and the record_change trigger unbound (bound=false)", + "POST another priority='urgent' showcase_task; GET the runs list again — assert NO new run row was added (the kill switch)", + "toggle ON: POST /api/v1/automation/showcase_urgent_task_alert/toggle {enabled:true}; re-read /_status (enabled=true, bound=true again)", + "POST a third priority='urgent' showcase_task; GET the runs list — a new run row appears again", + "negative probe (unknown flow): POST /api/v1/automation/does_not_exist/toggle {enabled:false} — expect a not-found error, no state change" + ], + "acceptance": [ + { + "clause": "baseline: an urgent task created BEFORE any toggle fires the flow exactly once", + "oracle": "api", + "verify": "GET /automation/showcase_urgent_task_alert/runs after the first urgent create shows one new run whose trigger records the record_change mutation on that task", + "evidence": "runs list + the created task id" + }, + { + "clause": "toggle OFF is a kill switch: after {enabled:false}, a mutation that used to fire the flow produces NO new run row", + "oracle": "api", + "verify": "the runs list count is IDENTICAL before and after the second urgent create — assert the ABSENCE of a new run, not merely that a run 'looks skipped'", + "evidence": "before/after runs reads bracketing the second create" + }, + { + "clause": "the runtime state mirrors the toggle: /_status reports enabled=false and the record_change trigger unbound while off, enabled=true + bound while on", + "oracle": "api", + "verify": "GET /api/v1/automation/_status (getFlowRuntimeStates) for showcase_urgent_task_alert flips enabled/bound across the OFF and ON toggles — the toggle unbinds the trigger, it does not merely guard execute()", + "evidence": "the three /_status reads" + }, + { + "clause": "toggle ON restores firing: after {enabled:true}, the next urgent task fires the flow again", + "oracle": "api", + "verify": "the runs list gains exactly one new run after the third urgent create (and none was added while off) — firing resumed only after the ON toggle", + "evidence": "runs list after the re-enable" + } + ], + "negative": [ + "a new run row appearing while the flow is toggled OFF is a FAIL — the kill switch must unbind the trigger (deactivateFlowTrigger), not just guard the run; a run that fired anyway means the event source was never detached", + "a toggle that answers 2xx while /_status still reports enabled/bound unchanged is a FAIL — the reported state must match the enforced state", + "toggling an unknown flow that answers 2xx (rather than not-found) is a FAIL" + ], + "traps": ["seed-data-thin", "dispatcher-vs-hono-route"], + "source": [ + "packages/runtime/src/route-ledger.ts (POST /automation/:name/toggle → automation.toggle; GET /automation/_status → automation.getRuntimeStatus)", + "packages/services/service-automation/src/engine.ts (toggleFlow → flowEnabled + activateFlowTrigger/deactivateFlowTrigger; getFlowRuntimeStates enabled/bound)", + "examples/app-showcase/src/automation/flows/index.ts (UrgentTaskAlertFlow — the record_change flow, #3427)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "initial — pins the /toggle runtime kill switch on a record_change flow: OFF unbinds the trigger so the firing mutation produces no run, ON restores it; runs list + runtime state as oracles", "ref": "claude/platform-test-checklist-ocwugl" } + ] } ] } diff --git a/docs/qa/platform-checklist/areas/cli.json b/docs/qa/platform-checklist/areas/cli.json new file mode 100644 index 0000000000..2868a357a8 --- /dev/null +++ b/docs/qa/platform-checklist/areas/cli.json @@ -0,0 +1,511 @@ +{ + "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md.", + "area": "cli", + "title": "CLI — os dev/build/migrate/verify boot & exit-code contracts, scaffold first-run, flag/command error UX", + "items": [ + { + "id": "cli.dev-boot-contract", + "title": "os dev boots to healthy with a loginable seeded admin, honest DB selection, reported port shifts, and a staleness warning", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P0", + "surface": "cli", + "personas": ["operator (local shell)"], + "fixtures": { + "app": "showcase", + "requires": [ + "a free non-default port (lsof -nP -iTCP: -sTCP:LISTEN empty first — dogfood skill §0) and a scratch DB path under /tmp//" + ] + }, + "steps": [ + "from examples/app-showcase run `objectstack dev --ui --seed-admin -p -d file:/tmp//data.db` and capture the full boot log (Environment ID / Artifact / Database key-values print at boot)", + "poll `curl -s -m3 -o /dev/null -w '%{http_code}' http://localhost:/api/v1/health` until 200, then GET /api/v1/ready; open /_console/ and sign in as admin@objectos.ai / admin123", + "stop; restart the SAME command against the SAME DB but with `--admin-password changed99`; attempt login with the ORIGINAL password (the seed is empty-DB-only and must not overwrite)", + "stop; in a scratch dir with NO objectstack.config.ts and no --artifact, run `os dev; echo $?` and capture the message", + "back in the app dir with the server DOWN, touch a src file so it is newer than dist/objectstack.json, boot WITHOUT --compile, and capture the staleness warning block", + "with instance A still running, boot instance B on the same requested port and capture the '↪ server bound to port (requested )' line", + "run once with NONE of --database/--fresh/env-db set and record the printed Database line (must be the project-anchored file:/.objectstack/data/dev.db); then run with `--fresh` and record the '🧪 Fresh OS_HOME' tempdir line and its deletion on exit", + "capture `echo $?` after every terminated invocation" + ], + "acceptance": [ + { + "clause": "the boot reaches health: /api/v1/health answers 200 and /api/v1/ready succeeds, with the console served at /_console when --ui is passed", + "oracle": "api", + "verify": "the health poll flips to 200 within the probe budget; ready returns success; /_console/ serves the login page", + "evidence": "curl outputs + console screenshot" + }, + { + "clause": "--seed-admin provisions the FIXED well-known dev admin (admin@objectos.ai / admin123) loginable via the real auth endpoint, and the seed is idempotent — a later boot with a different --admin-password does NOT overwrite the existing account", + "oracle": "api", + "verify": "login succeeds with the original credentials after the changed-password restart (flag contract: 'only acts on a zero-user DB, never overwrites an existing account')", + "evidence": "the two auth responses" + }, + { + "clause": "DB selection honors the resolveDefaultDevDbUrl matrix: with nothing chosen, dev defaults to the PERSISTENT project-anchored sqlite file (.objectstack/data/dev.db) — never the serve default of :memory: that wipes work on restart", + "oracle": "log", + "verify": "the printed Database key-value per variant matches the matrix (default file path; -d url; --fresh tempdir; env url; memory driver imposes no file default)", + "evidence": "the per-variant Database boot lines" + }, + { + "clause": "--fresh isolates OS_HOME-keyed state in an auto-deleted tempdir — and the evidence must NOT claim isolation for app-declared cwd-relative paths, which survive by documented design (#5594, e.g. the showcase-external datasource file)", + "oracle": "log", + "verify": "the Fresh OS_HOME line names a tempdir; the dir is gone after exit; any surviving .objectstack/data/showcase_external.db is annotated as the documented #5594 carve-out, not filed as a bug", + "evidence": "boot line + post-exit directory listings" + }, + { + "clause": "a busy requested port auto-shifts AND is reported — the actually-bound port is printed, never silently different", + "oracle": "log", + "verify": "instance B prints '↪ server bound to port (requested )' (the IPC objectstack:listening channel exists so the parent can print the truth)", + "evidence": "the log line + a health probe on the actual port" + }, + { + "clause": "a stale artifact is called out at boot: when dist/objectstack.json is older than the sources, the boot warns loudly, names the newest source and the remedy — and still boots (warn, never gate — #5148)", + "oracle": "log", + "verify": "the '⚠ … is OLDER than your sources — this boot serves the STALE build' block prints with the newest-source path and the fix line; the server still comes up", + "evidence": "the warning block + subsequent healthy boot" + }, + { + "clause": "negative: no config and no artifact exits 1 with the remedy ('Run in a directory with objectstack.config.ts, pass --artifact , or run from the monorepo root.') — never a hang, never a silent 0", + "oracle": "log", + "verify": "echo $? prints 1 and stderr carries the remedy line", + "evidence": "captured stderr + exit code" + } + ], + "negative": [ + "a dev boot that wipes an existing dev DB when the user chose nothing (a regression to the :memory: serve default) is the FAIL the persistent default exists for", + "an auto-shifted port that is not reported (URL printed for the requested port while the server bound elsewhere) is a FAIL" + ], + "variants": [ + "default: file:/.objectstack/data/dev.db (persistent, imposed only when nothing else chosen)", + "--database (explicit; no default imposed)", + "--fresh (ephemeral tempdir OS_HOME; implies --seed-admin)", + "OS_DATABASE_URL / DATABASE_URL env (env wins over the default)", + "--database-driver memory / OS_DATABASE_DRIVER=memory (explicit in-memory; no file default)" + ], + "traps": ["stale-dist", "stale-console-bundle", "shared-browser-tab"], + "source": [ + "packages/cli/src/commands/dev.ts (resolveDefaultDevDbUrl matrix; --fresh coverage note #5594; seed-admin idempotency contract; IPC bound-port report; #5148 staleness warning + rebuild-restart coordinator)", + ".claude/skills/dogfood-verification/SKILL.md §0–§1 (port isolation, health probe, fixed admin creds, /_console layout)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new area item: the os dev boot contract read directly out of dev.ts (DB-selection matrix as variants, seed idempotency, port-shift reporting, #5148 staleness warning, #5594 fresh-isolation carve-out) plus the dogfood skill's boot shapes", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "cli.build-own-contract", + "title": "os build's own contract: exit 0/1 only, located errors for schema and author-time rule failures, artifact + stats output, warnings never flip the exit", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "build", + "personas": ["operator (local shell)"], + "fixtures": { + "app": "showcase", + "requires": [ + "a scratch copy of a known-good app config (scaffolded blank app or a worktree copy of examples/app-showcase) so deliberate breakage never touches shared fixtures" + ] + }, + "steps": [ + "on the clean config run `os build; echo $?` and capture the summary (metadata stats, Artifact path/size)", + "run `os build --json` and capture the success payload", + "break the schema: give one field an impossible shape (e.g. delete its type) and run `os build; echo $?` — capture the located Zod error", + "break an author-time rule: author a flow whose Approval node approver expression does not parse (the #4409 worked example that once built green while os lint rejected it) and run `os build; echo $?`", + "re-run both failing builds with --json and capture the failure payloads", + "restore the config, add one UNDECLARED authoring key (the #3786 advisory class) and run `os build; echo $?` — the warning prints, the build passes", + "confirm `os build` and `os compile` produce identical behavior on the same input (build is the documented alias)" + ], + "acceptance": [ + { + "clause": "success: exit 0, dist/objectstack.json written, and the summary reports metadata stats + artifact size; --json answers { success: true, output, size, stats, warnings, conversions, duration }", + "oracle": "build", + "verify": "echo $? is 0, the artifact file exists and parses, and the --json payload carries the declared keys", + "evidence": "exit code + artifact listing + the JSON payload" + }, + { + "clause": "a schema violation fails the build with exit 1 and a LOCATED error naming the failing path — never a bare 'validation failed'", + "oracle": "build", + "verify": "the formatted Zod error names the object/field path of the deliberate break; echo $? is 1", + "evidence": "the error output + exit code" + }, + { + "clause": "author-time rule failures exit 1 and every finding carries where/message/hint/rule/path — and ALL failing rules report at once, not first-failure-only", + "oracle": "build", + "verify": "the #4409 registry output for the broken-approver fixture shows the located finding with its rule id and hint ('the build is the command that SHIPS' — it must be no weaker than validate/lint)", + "evidence": "the rule-failure output + exit code" + }, + { + "clause": "--json failure and advisory shapes match os validate --json (same warnings/conversions keys) — the #3782 parity class: the two surfaces must not disagree about what an author is told", + "oracle": "log", + "verify": "the failure payload is { success: false, … issues } and success payloads carry warnings + conversions under the same keys validate emits", + "evidence": "the paired --json payloads" + }, + { + "clause": "exit codes are exactly 0 or 1 (the CliExitCode union) — never a count, never a duration", + "oracle": "build", + "verify": "echo $? across all runs is only ever 0 or 1 (the type that pins the #4873 class for every emitJson caller)", + "evidence": "the collected exit codes" + }, + { + "clause": "advisories never flip the exit: the undeclared-authoring-key build warns visibly AND exits 0 — both sides of the warn/fail line", + "oracle": "build", + "verify": "the #3786 warning block prints, the artifact is written, echo $? is 0", + "evidence": "warning output + exit code + artifact" + } + ], + "negative": [ + "exit 0 with no artifact written, or nonzero on the clean config, is a FAIL", + "the DEEP gate content (date-arithmetic formula errors, retired-key tombstones) is api-backend.formula-gates / api-backend.enforce-or-remove-authoring-gates — cite pinned passes there; this item owns only the build's OWN exit/error/output contract" + ], + "traps": ["stale-dist"], + "source": [ + "packages/cli/src/commands/compile.ts (the full gate pipeline: Zod parse, #4409 author-time rule registry, #3786 unknown-key advisory, --json shapes, #3782 conversion-notice parity) + build.ts (alias)", + "packages/cli/src/utils/format.ts (CliExitCode = 0 | 1 — the narrowed exit-code slot)", + "api-backend.formula-gates, api-backend.enforce-or-remove-authoring-gates (gate content — cross-referenced, not duplicated)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: the build command's own contract (exit-code discipline, located schema + rule errors, --json parity, advisory both-sides) read from compile.ts, with the gate-content items cross-referenced instead of re-proven", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "cli.migrate-plan-apply-json", + "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": 1, + "priority": "P1", + "surface": "cli", + "personas": ["operator (local shell)"], + "fixtures": { + "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" + ] + }, + "steps": [ + "boot the scratch app once with `os dev -d file:/tmp//migrate.db` so the base schema exists; stop it", + "add a new field to one object in the config and `os build`; run bare `os migrate` twice — both runs must print the SAME pending add-column plan and mutate nothing", + "run `os migrate plan --json; echo $?` and capture the payload ({ database, managedTables, total, changes, pending })", + "run `os migrate apply --yes; echo $?` and capture the applied list; then `os migrate plan` again (expect in-sync) and `os migrate apply --json --yes; echo $?` (expect message in_sync) — the idempotent re-run", + "remove a field from the config + `os build`; run `os migrate apply --yes` WITHOUT --allow-destructive (the drop must be SKIPPED with a warning), then with `--allow-destructive --yes` (applied)", + "with the dev server booted and holding the DB, run `os migrate apply --yes; echo $?` (expect the busy refusal, exit 1, without --force) and `os migrate plan` (proceeds with a warning — a plan writes nothing either way)", + "run `os migrate apply --json` WITHOUT --yes on a pending change: the payload must say confirmation_required with the pass--yes hint and mutate nothing", + "the #4873 pair: on the scratch DB run `os migrate recorded-by --json; echo $?` and `os migrate resume --json; echo $?`" + ], + "acceptance": [ + { + "clause": "bare `os migrate` is the dry-run plan and NEVER mutates: boot-time DDL is deferred and the artifact seed suppressed (#3917), so two consecutive plans report identical drift and the physical schema is unchanged", + "oracle": "log", + "verify": "the two plan outputs match; a schema dump (or third plan) after them equals the first", + "evidence": "the two plan outputs + schema check" + }, + { + "clause": "plan --json emits the declared shape and exits 0", + "oracle": "log", + "verify": "payload carries database/managedTables/total/changes/pending; echo $? is 0", + "evidence": "payload + exit code" + }, + { + "clause": "#4873 exit-code honesty: EVERY migrate subcommand with --json exits 0 on success — never the elapsed-ms leak (recorded-by/resume once passed timer.elapsed() into the exit-code slot, so the shell saw duration & 0xFF: a different bogus nonzero on every successful run)", + "oracle": "log", + "verify": "echo $? after plan/apply/recorded-by/resume --json successes is exactly 0; the CliExitCode 0|1 type now makes the mistake a compile error", + "evidence": "the collected exit codes per subcommand" + }, + { + "clause": "apply is safe-by-default: a destructive change is SKIPPED with an explicit warning until --allow-destructive, then applied with it — both sides captured", + "oracle": "log", + "verify": "the drop appears under skipped (with the re-run hint) on the first apply and under applied on the --allow-destructive run", + "evidence": "the two apply outputs" + }, + { + "clause": "idempotent re-run: after a successful apply, plan reports in-sync ('nothing to migrate') and apply --json answers message in_sync with exit 0", + "oracle": "log", + "verify": "the post-apply plan prints the in-sync success line; the JSON re-apply payload is { …, message: 'in_sync' } and echo $? is 0", + "evidence": "outputs + exit codes" + }, + { + "clause": "a busy database refuses apply (exit 1) without --force, while plan proceeds with only a warning — the read/write asymmetry is deliberate", + "oracle": "log", + "verify": "apply against the server-held DB exits 1 naming the occupancy; plan against the same DB completes with the busy warning", + "evidence": "both outputs + exit codes" + }, + { + "clause": "--json is non-interactive: a mutating apply without --yes reports confirmation_required + the hint and performs NO change", + "oracle": "log", + "verify": "the payload says confirmation_required, hint 'pass --yes'; a follow-up plan still shows the pending change", + "evidence": "payload + follow-up plan" + } + ], + "negative": [ + "an apply that performs a drop WITHOUT --allow-destructive is the FAIL the safe-by-default split exists for", + "exit 0 from a refused (busy) apply, or ANY nonzero exit from a successful --json run, is a FAIL — exit codes are the load-bearing CI contract here" + ], + "variants": [ + "plan (default of the bare command)", + "apply", + "resume", + "recorded-by", + "meta", + "files-to-references", + "summary-nulls", + "value-shapes" + ], + "traps": ["stale-dist"], + "automated": { "kind": "unit", "ref": "packages/cli/src/utils/format.exit-code.test.ts" }, + "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 narrows the emitJson exit slot — the #4873 fix, commit 83df2fd)", + "packages/cli/src/commands/migrate/ (the eight registered subcommands enumerated as variants)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: the migrate topic's read-only/apply/idempotency contract from plan.ts+apply.ts, with #4873 --json exit-code honesty as a load-bearing clause pinned to format.exit-code.test.ts and the subcommand set enumerated from src/commands/migrate/", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "cli.verify-verdict-exit-mapping", + "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": 1, + "priority": "P1", + "surface": "cli", + "personas": ["operator (local shell)"], + "fixtures": { + "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" + ] + }, + "steps": [ + "from examples/app-showcase run `objectstack verify --json > report.json; echo $?`", + "assert every results[].status in report.json is one of the six declared verdicts, and recompute the summary counts from results[] (they must reconcile)", + "recompute hardFailures = createFailed + readFailed + fidelityGaps (+ rls holes when --rls ran) from the report and compare to the exit code", + "run `objectstack verify --rls --json; echo $?` (the RLS proofs boot a SEPARATE fresh stack by design — unique-constraint collisions with the fidelity phase would silently skip objects)", + "run `OS_TENANCY_POSTURE=isolated objectstack verify --json` and capture either multiTenant:true in the payload or the hard boot error — never a quiet single-org run (#5262)", + "stage the fail side per the knownGap and capture `echo $?` (expect 1) alongside the failing report", + "capture the human-format run too (`objectstack verify`) and check the ✓/✗ banner agrees with the recomputed hardFailures" + ], + "acceptance": [ + { + "clause": "the verdict vocabulary is closed: every per-object status is exactly one of verified | fidelity-gaps | create-failed | read-failed | skipped | needs-fixture", + "oracle": "log", + "verify": "scan results[].status in the JSON report against the six-member union in packages/verify/src/verify.ts", + "evidence": "report.json" + }, + { + "clause": "exit code is nonzero EXACTLY when hardFailures > 0, where hardFailures = createFailed + readFailed + fidelityGaps + rlsHoles — recomputed from the report, never trusted from the banner", + "oracle": "log", + "verify": "echo $? is 0 when the recomputed sum is 0 and 1 when it is positive (the staged failing run)", + "evidence": "exit codes paired with recomputed sums" + }, + { + "clause": "the inconclusive split is honored: a 400 VALIDATION_FAILED on the auto-derived record is classified needs-fixture (a fixture gap, not a platform finding) and does NOT fail the run; a 5xx is create-failed and DOES", + "oracle": "log", + "verify": "needs-fixture and skipped counts are excluded from hardFailures in the report vs exit-code comparison; the classification comment in verify.ts is the contract", + "evidence": "report excerpt showing needs-fixture objects on an exit-0 run" + }, + { + "clause": "--rls runs its proofs on a separate fresh stack and any hole fails the run", + "oracle": "log", + "verify": "the --rls report includes the rls section; rls.summary.holes participates in hardFailures", + "evidence": "the --rls report + exit code" + }, + { + "clause": "#5262 posture honesty: a walled OS_TENANCY_POSTURE (isolated|group) makes verify boot org-scoped, or hard-fail when the enterprise runtime is missing — NEVER a quiet single-org pass that under-verifies ('a verifier that under-verifies reports success it never established')", + "oracle": "log", + "verify": "the payload shows multiTenant:true OR the run errors loudly; multiTenant:false under a walled posture is the pinned regression", + "evidence": "the posture run's payload or error" + } + ], + "negative": [ + "exit 0 while the report carries fidelityGaps > 0 (or any create/read failure) is a FAIL of the verifier's own honesty contract", + "a quiet single-org boot under a walled posture is the #5262 regression (third recurrence of the shape: cloud#1020, #5233) — FAIL" + ], + "variants": [ + "verified", + "fidelity-gaps (hard failure)", + "create-failed (hard failure)", + "read-failed (hard failure)", + "needs-fixture (inconclusive — never fails)", + "skipped (inconclusive — never fails)" + ], + "traps": ["stale-dist", "wrong-persona"], + "automated": { "kind": "unit", "ref": "packages/cli/src/commands/verify-tenancy-posture.test.ts" }, + "source": [ + "packages/cli/src/commands/verify.ts (hardFailures sum, exit contract, resolveVerifyMultiTenant / #5262 / ADR-0105 D1, separate RLS stack rationale)", + "packages/verify/src/verify.ts (the six-member status union; 400-VALIDATION_FAILED→needs-fixture vs 5xx→create-failed classification)", + "packages/verify/src/rls.ts (#1994 class: you can't write what you can't read)" + ], + "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" } + ] + }, + { + "id": "cli.scaffold-first-run", + "title": "The published first-run closes: create-objectstack scaffold → install → validate → build → boot → health, with the skills boundary holding", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["new user (published-registry consumer)"], + "fixtures": { + "app": "showcase", + "requires": ["npm registry reachability (the whole point is the PUBLISHED path, not the repo checkout)"], + "knownGaps": [ + "during an RC window the repo version is unpublished and `latest` points at the previous major, so the protocol-major handshake refuses the artifact — that refusal is the ADR-0087 D1 gate WORKING on a skew the fallback introduced (#4894); record it in evidence, do not fail the item for it" + ] + }, + "steps": [ + "in a scratch dir: `npx -y create-objectstack@latest qa-first-run -t blank --skip-skills`", + "cd qa-first-run && npm install --no-fund --no-audit; echo $?", + "npm run validate; echo $? — then npm run build; echo $?", + "boot from the artifact exactly as the workflow does: `npx os start --artifact ./dist/objectstack.json --port 8080 > server.log 2>&1 &`, poll /api/v1/health up to 60s, then `curl -fsS http://localhost:8080/api/v1/ready`", + "repeat scaffold + validate + build across the template matrix (variants) — remote templates always ship build; run validate where the script exists", + "skills boundary probe: in a fresh scaffold run `npx -y skills add /skills --all --copy` and compare the installed set to the curated skills/ catalog; then list repo-root discovery and assert no internal skill (dogfood-verification) surfaces", + "capture server.log for any boot error" + ], + "acceptance": [ + { + "clause": "scaffold → install → validate → build → boot → health closes with every step exit 0, health 200 and ready succeeding", + "oracle": "api", + "verify": "the per-step exit codes are 0 and the two probes succeed against the booted artifact", + "evidence": "step exit codes + curl outputs + server.log" + }, + { + "clause": "every published template validates and builds — per-variant, no template inferred from a sibling", + "oracle": "build", + "verify": "validate (where present) and build exit 0 for each of the six templates", + "evidence": "per-template exit codes" + }, + { + "clause": "the scaffolded artifact boots via os start --artifact: the ADR-0087 D1 protocol-major handshake ACCEPTS a coherent scaffold — and when the RC-window skew applies, it REFUSES with the named engines.protocol mismatch instead of booting wrong", + "oracle": "log", + "verify": "either the healthy boot, or the exact '✗ package … targets protocol ^N … but this runtime is protocol M' refusal recorded as the gate working (#4894)", + "evidence": "server.log excerpt" + }, + { + "clause": "the skills boundary holds: the installed set equals the curated skills/ catalog and repo-root discovery surfaces NO internal skill (the 15.1 third-party-eval leak, pinned in the workflow)", + "oracle": "log", + "verify": "set-equality against the curated catalog; grep for dogfood-verification in the discovery listing comes back empty", + "evidence": "the installed-set diff + discovery listing" + }, + { + "clause": "negative: a server that never becomes healthy within the probe budget is a FAIL carrying server.log — not a retry-until-green", + "oracle": "log", + "verify": "on timeout the run records the failure with the full server.log, mirroring the workflow's '::error::server never became healthy' branch", + "evidence": "server.log on any failure" + } + ], + "negative": [ + "an internal skill appearing in a scaffolded project is the exact leak the boundary step exists for — FAIL", + "a template that builds only from the repo checkout but not from the registry is the #2908 class this whole item guards" + ], + "variants": ["blank", "todo", "compliance", "content", "contracts", "procurement"], + "traps": ["stale-dist"], + "automated": { "kind": "ci", "ref": ".github/workflows/scaffold-e2e.yml" }, + "source": [ + ".github/workflows/scaffold-e2e.yml (#2908 — the scaffold→install→validate→build→boot→health lane, the registry-canary template matrix, the skills-boundary assertions, the #4894 RC-window fallback)", + "packages/create-objectstack (the scaffolder under test)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: the published first-run experience mirrored step-for-step from scaffold-e2e.yml, template matrix as variants, RC-window protocol refusal recorded as gate-working instead of failure", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "cli.flag-command-error-ux", + "title": "Wrong flags and unknown commands error with usage and a nonzero exit — never silently ignored, never executed anyway", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "cli", + "personas": ["operator (local shell)"], + "fixtures": { + "app": "showcase", + "requires": ["any directory — the probes must not depend on a valid app config, since parse errors fire before command bodies run"] + }, + "steps": [ + "`os migrate plan --frobnicate; echo $?` — capture stderr, and assert the command body never ran (no 'Migrate · plan' header, no 'Booting schema stack' line)", + "`os dev --log-level bogus; echo $?` — an enum-valued flag with an out-of-set value", + "`os frobnicate; echo $?` — an unknown top-level command", + "`os environments frobnicate; echo $?` — an unknown subcommand of a registered topic", + "`os --help; echo $?` and `os migrate --help; echo $?` — the help side must exit 0", + "sweep the registered surface: for every variant run `os --help; echo $?` and record exit code + presence of a USAGE block", + "collect all outputs and exit codes into one evidence table" + ], + "acceptance": [ + { + "clause": "an unknown flag errors NAMING the flag (oclif: 'Nonexistent flag: --frobnicate') with a help pointer, exits nonzero (oclif parse errors exit 2) — and the command body never runs", + "oracle": "log", + "verify": "stderr names --frobnicate and points at --help; echo $? is nonzero; the plan header/boot line is absent from the output", + "evidence": "captured stderr + exit code + absence check" + }, + { + "clause": "an out-of-set value for an enum flag errors listing the allowed set, exit nonzero", + "oracle": "log", + "verify": "the --log-level bogus error enumerates debug|info|warn|error|fatal|silent; echo $? is nonzero", + "evidence": "stderr + exit code" + }, + { + "clause": "an unknown command or topic-subcommand errors 'command … not found' with exit nonzero — never treated as a default command (this CLI ships help+plugins only, no not-found suggester: a typo is a hard error, not a did-you-mean prompt)", + "oracle": "log", + "verify": "both the top-level and topic probes error naming the unknown command; echo $? is nonzero for each", + "evidence": "the two stderr captures + exit codes" + }, + { + "clause": "--help exits 0 with a usage block for the root and for EVERY registered command and topic — per-variant, none inferred", + "oracle": "log", + "verify": "the sweep records exit 0 + a USAGE section for all 30 variants", + "evidence": "the sweep's exit-code/usage table" + } + ], + "negative": [ + "a parse error that still executes the command (output shows the command ran after the flag error) is the silent-ignore FAIL this item exists for", + "exit 0 on any unknown-flag/unknown-command probe is a FAIL even if an error message printed" + ], + "variants": [ + "build", + "compile", + "create", + "dev", + "diff", + "doctor", + "explain", + "generate", + "info", + "init", + "lint", + "login", + "logout", + "register", + "serve", + "start", + "test", + "validate", + "verify", + "whoami", + "cloud (topic)", + "data (topic)", + "datasource (topic)", + "db (topic)", + "environments (topic)", + "i18n (topic)", + "meta (topic)", + "migrate (topic)", + "package (topic)", + "plugin (topic)" + ], + "source": [ + "packages/cli/src/commands/ (the 20 top-level commands + 10 topics enumerated as variants — oclif pattern discovery per package.json oclif.commands)", + "packages/cli/package.json (oclif.plugins = help + plugins only — no not-found plugin, so unknown commands hard-error)", + "@oclif/core parse contract (Nonexistent flag / enum FailedFlagValidation / command-not-found, exit 2)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: flag/command error UX with the full registered command surface enumerated from src/commands/ as variants, and never-executed-anyway as the load-bearing negative", "ref": "claude/platform-test-checklist-ocwugl" } + ] + } + ] +} diff --git a/docs/qa/platform-checklist/areas/dashboards.json b/docs/qa/platform-checklist/areas/dashboards.json index df8bf781fd..3f417ed2ca 100644 --- a/docs/qa/platform-checklist/areas/dashboards.json +++ b/docs/qa/platform-checklist/areas/dashboards.json @@ -11,7 +11,9 @@ "revision": 2, "priority": "P1", "surface": "mixed", - "personas": ["admin"], + "personas": [ + "admin" + ], "fixtures": { "app": "showcase", "requires": [ @@ -83,15 +85,27 @@ "measures", "chartFlavour (hallucinated control)" ], - "traps": ["stale-console-bundle"], + "traps": [ + "stale-console-bundle" + ], "source": [ "#3358 §3 (four stray-key variants all rejected with the named error)", "packages/spec/src/ui/dashboard.zod.ts (LEGACY key list + strict error map, ADR-0021 single-form cutover)", "packages/spec/src/ui/chart.zod.ts (ChartConfigSchema wrong-layer guidance: dataset/aggregate/objectName/drillDown)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from #3358", + "ref": "#3358" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -102,7 +116,9 @@ "revision": 2, "priority": "P2", "surface": "browser", - "personas": ["admin"], + "personas": [ + "admin" + ], "fixtures": { "app": "showcase", "requires": [ @@ -146,15 +162,28 @@ "negative": [ "a chart area that stays blank until the window is resized is the FAIL this item exists for, even if it draws afterwards" ], - "traps": ["single-datapoint", "hydration-race"], + "traps": [ + "single-datapoint", + "hydration-race" + ], "source": [ "#3358 §3 — note its caveat: with thin seeds every widget draws one data point, so the tick 'says less than it looks'; prefer multi-bucket fixtures", "examples/app-showcase/src/ui/dashboards/chart-gallery.dashboard.ts", "examples/app-showcase/src/ui/dashboards/ops-dashboard.dashboard.ts" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358 with the thin-seed caveat attached", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from #3358 with the thin-seed caveat attached", + "ref": "#3358" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -165,7 +194,9 @@ "revision": 2, "priority": "P2", "surface": "browser", - "personas": ["admin"], + "personas": [ + "admin" + ], "fixtures": { "app": "showcase", "requires": [ @@ -217,7 +248,10 @@ "negative": [ "a drill that opens the object's FULL unfiltered list while looking scoped (heading says the bucket, rows say everything) is a FAIL — the network trace is the authority, not the heading" ], - "traps": ["seed-data-thin", "single-datapoint"], + "traps": [ + "seed-data-thin", + "single-datapoint" + ], "source": [ "#3358 §3", "#3415", @@ -226,8 +260,18 @@ "examples/app-showcase/src/ui/reports/index.ts (drilldown: true on all four reports)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; runnable once multi-bucket seeds exist", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from #3358; runnable once multi-bucket seeds exist", + "ref": "#3358" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -235,15 +279,18 @@ "title": "Every ChartTypeSchema member renders a real chart with the correct marks and series count", "since": "v15", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "browser", - "personas": ["admin"], + "personas": [ + "admin" + ], "fixtures": { "app": "showcase", "requires": [ "the Chart Gallery dashboard (showcase_chart_gallery) — one widget per DISTINCTLY-rendered family, bound to showcase_task_metrics / showcase_project_metrics (ADR-0021)", - "a scratch draft dashboard (via PUT /api/v1/meta/dashboard/qa_single_value_gallery?mode=draft) for the four single-value synonym types the gallery deliberately does not duplicate" + "a scratch draft dashboard (via PUT /api/v1/meta/dashboard/qa_single_value_gallery?mode=draft) for the four single-value synonym types the gallery deliberately does not duplicate", + "a scratch draft dashboard for the period-over-period compareTo probe (via PUT /api/v1/meta/dashboard/qa_compareto_probe?mode=draft) — NO showcase widget authors compareTo, so this is authored fresh exactly like the strict-widget probes; bind a metric/line widget to showcase_task_metrics with a time dimension (a month-bucketed created_at) and carry compareTo: { kind: 'previousPeriod' }" ], "knownGaps": [ "kpi / gauge / solid-gauge / bullet have NO distinct renderer today — they render the same single value as `metric` (chart.zod.ts NOTE: 'honest single-value synonyms … gain a dial when a gauge renderer lands'; coverage.test.ts SAME_AS_METRIC). For these four the demonstrable claim is 'renders the value, not blank', NOT 'renders a dial' — a run must not tick a dial that does not exist" @@ -259,6 +306,7 @@ "for table and pivot, verify real grouped tables: Projects by Account rows == distinct accounts with 3 measure columns; Tasks by Status × Priority cross-tab dimensions match distinct status × priority", "author the scratch draft dashboard with four widgets of types kpi, gauge, solid-gauge, bullet, each bound to dataset showcase_task_metrics, values ['task_count'], publish it, and load it", "verify each of the four renders the same numeric value the metric tile shows — a number, never a blank tile", + "author qa_compareto_probe with a time-dimensioned widget carrying compareTo: { kind: 'previousPeriod' }, publish it, and load it — verify a comparison series/delta actually RENDERS (not the base numbers alone with the comparison silently dropped, the pre-#5011 ADR-0021 dataset-path bug)", "capture the per-variant verdict table" ], "acceptance": [ @@ -303,11 +351,24 @@ "oracle": "test", "verify": "run examples/app-showcase/test/coverage.test.ts ('covers every distinctly-renderable ChartType'); it enumerates ChartTypeSchema minus SAME_AS_METRIC against the gallery's widgets. Note: this pins declaration coverage only — it does NOT replace the browser render checks above", "evidence": "the test output" + }, + { + "clause": "period-over-period compareTo renders the comparison: a widget authoring compareTo: { kind: 'previousPeriod' } on the scratch draft draws a comparison series/delta beside its base measure — the ADR-0021 dataset path that used to silently DROP the string arm now carries the converged { kind, dimension? } shape (#5011)", + "oracle": "screenshot", + "verify": "the qa_compareto_probe widget shows a base value AND a previous-period comparison (series/delta), not the base alone; DOM read only after the screenshot confirms render", + "evidence": "the compareTo widget screenshot" + }, + { + "clause": "the converged compareTo contract is pinned at the parse: { kind: 'previousPeriod' | 'previousYear', dimension? } is accepted THROUGH the dashboard metadata root, while the retired spellings (the bare 'previousPeriod' string, { offset: '7d' }) are rejected with the #5011 upgrade in hand — a strict schema nobody parses would gate nothing", + "oracle": "test", + "verify": "run packages/spec/src/ui/dashboard-compareto.test.ts (#5011) — it asserts the converged shape parses via getMetadataTypeSchema('dashboard') and every retired spelling is rejected with its prescription at the top level; evidence is the test output", + "evidence": "the dashboard-compareto.test.ts run output" } ], "negative": [ "any variant rendering as a DIFFERENT family than named (e.g. sankey falling back to a bar) is a FAIL — advertising a type that renders as something else is the exact failure the trimmed enum exists to prevent", - "a blank widget with no error for any variant is a FAIL" + "a blank widget with no error for any variant is a FAIL", + "a compareTo widget rendering its base numbers with the comparison silently absent (the pre-#5011 dropped-string-arm bug) is a FAIL, not a thin-data caveat" ], "variants": [ "bar", @@ -331,15 +392,32 @@ "table", "pivot" ], - "traps": ["single-datapoint", "hydration-race", "stale-console-bundle"], + "traps": [ + "single-datapoint", + "hydration-race", + "stale-console-bundle" + ], "source": [ "packages/spec/src/ui/chart.zod.ts (ChartTypeSchema — 20 members; NOTE on trimmed variants and single-value synonyms)", "examples/app-showcase/src/ui/dashboards/chart-gallery.dashboard.ts", "examples/app-showcase/src/ui/datasets/chart-gallery.dataset.ts", - "examples/app-showcase/src/coverage.ts + examples/app-showcase/test/coverage.test.ts (SAME_AS_METRIC waiver)" + "examples/app-showcase/src/coverage.ts + examples/app-showcase/test/coverage.test.ts (SAME_AS_METRIC waiver)", + "packages/spec/src/ui/dashboard-compareto.test.ts (#5011 — compareTo converged on the executor's { kind, dimension? } contract; parses through the dashboard root; retired spellings rejected with the upgrade)", + "packages/spec/src/ui/dashboard.zod.ts (DashboardWidgetSchema compareTo slot — union-free strict object so its prescription reaches the wire)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "new matrix item: per-variant render proof for the full ChartTypeSchema enum, grounded in the Chart Gallery + coverage ratchet", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "new matrix item: per-variant render proof for the full ChartTypeSchema enum, grounded in the Chart Gallery + coverage ratchet", + "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 2, + "date": "2026-08-08", + "change": "added the period-over-period compareTo: { kind: 'previousPeriod' } clause + scratch-draft probe + parse pin (dashboard-compareto.test.ts #5011)", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -350,7 +428,9 @@ "revision": 1, "priority": "P1", "surface": "mixed", - "personas": ["admin"], + "personas": [ + "admin" + ], "fixtures": { "app": "showcase", "requires": [ @@ -426,9 +506,20 @@ "a joined block nested inside blocks[] must be rejected at parse (block type enum is tabular|summary|matrix — no recursion); a silent save is a FAIL", "a report or widget silently rendering while its dataset binding names nothing (blank-without-error) is a FAIL" ], - "variants": ["tabular (via ListView lens)", "summary", "matrix", "joined"], - "automated": { "kind": "e2e", "ref": "packages/qa/dogfood/test/dashboard-designer-roundtrip.dogfood.test.ts" }, - "traps": ["hydration-race", "seed-data-thin"], + "variants": [ + "tabular (via ListView lens)", + "summary", + "matrix", + "joined" + ], + "automated": { + "kind": "e2e", + "ref": "packages/qa/dogfood/test/dashboard-designer-roundtrip.dogfood.test.ts" + }, + "traps": [ + "hydration-race", + "seed-data-thin" + ], "source": [ "packages/spec/src/ui/report.zod.ts (ReportType enum; block type enum excludes joined)", "packages/spec/src/ui/dashboard.zod.ts (ADR-0021 dataset+dimensions+values single form)", @@ -438,7 +529,12 @@ "packages/qa/dogfood/test/dashboard-designer-roundtrip.dogfood.test.ts" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "new item: dataset-only authoring contract (ADR-0021) + ReportType variant matrix, pinned to the designer-roundtrip golden test", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "new item: dataset-only authoring contract (ADR-0021) + ReportType variant matrix, pinned to the designer-roundtrip golden test", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -449,7 +545,9 @@ "revision": 1, "priority": "P2", "surface": "mixed", - "personas": ["admin"], + "personas": [ + "admin" + ], "fixtures": { "app": "showcase", "requires": [ @@ -508,8 +606,14 @@ "a trend chart collapsing every row into one bucket while the seed spans multiple months is the #3773 regression returned — FAIL, not 'thin data'", "an empty window that leaves widgets showing previous (stale) values with no re-query in the trace is a FAIL" ], - "automated": { "kind": "conformance", "ref": "packages/qa/dogfood/test/date-bucket-parity-conformance.test.ts" }, - "traps": ["single-datapoint", "seed-data-thin"], + "automated": { + "kind": "conformance", + "ref": "packages/qa/dogfood/test/date-bucket-parity-conformance.test.ts" + }, + "traps": [ + "single-datapoint", + "seed-data-thin" + ], "source": [ "packages/qa/dogfood/test/date-bucket-parity-conformance.test.ts (#3773)", "packages/qa/dogfood/test/empty-group-bucket-parity.test.ts (#3839)", @@ -517,18 +621,25 @@ "examples/app-showcase/src/data/seed/index.ts (prospects carry no signed_on — the deliberate NULL population)" ], "history": [ - { "revision": 1, "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": 1, + "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" + } ] }, { "id": "dashboards.global-filters-rescope", - "title": "Dashboard-level dateRange + global filters re-scope every bound widget through its OWN field mapping; opted-out widgets stay fixed", + "title": "Dashboard-level dateRange + global filters re-scope every bound widget through its OWN field mapping; opted-out widgets stay fixed; and the Studio widget inspector AUTHORS those filterBindings", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "browser", - "personas": ["admin"], + "personas": [ + "admin" + ], "fixtures": { "app": "showcase", "requires": [ @@ -545,7 +656,11 @@ "confirm the opted-out KPI issued NO re-query and shows the unchanged all-time value", "cross-check one re-scoped widget's displayed aggregate against a direct API aggregate with the same filter", "repeat the global-filter check on Delivery Operations: the Task Status select must re-scope the KPI hero tiles, composing with each tile's own per-widget filter", - "screenshot before/after each filter change" + "screenshot before/after each filter change", + "D1 (dirty→Reset): on Revenue Pulse, confirm the filter bar shows NO Reset affordance in its pristine state; change a filter away from its defaultValue and confirm a Reset button (RotateCcw) appears; click it and confirm every filter returns to its default and the widgets re-query back", + "D3 ({value,label} options): open the region select and confirm its options render as {value,label} pairs (the trigger shows the LABEL, the committed value is the option value); capture the option-source request — a select whose options come from a dataset issues a server GROUP BY (queryDataset) for the distinct value list, not a truncated client dedupe", + "D2 (Studio authoring round-trip): in the Studio metadata-admin dashboard designer, select a widget and open the Filter Bindings section of the widget inspector; author a binding — re-target the dateRange (or region) filter to a specific field via the field-override combo, and toggle Apply OFF on another widget to opt it out; publish the draft", + "re-read the persisted metadata (GET /api/v1/meta/dashboard/) and confirm the authored widget.filterBindings shape landed; reload the dashboard and confirm the authored binding DRIVES the widget at runtime (re-targeted field in the re-query; opted-out widget no longer re-queries)" ], "acceptance": [ { @@ -583,22 +698,333 @@ "oracle": "api", "verify": "tile value equals the aggregate with BOTH filters applied", "evidence": "tile screenshot + query result" + }, + { + "clause": "D2 — the Studio widget inspector AUTHORS filterBindings that round-trip and drive the widget: a binding authored in the inspector's Filter Bindings section persists into widget.filterBindings and, on reload, re-scopes the widget through the authored field (or opts it out when Apply is unchecked) (objectui#2586)", + "oracle": "api", + "verify": "GET /api/v1/meta/dashboard/ after publish carries the authored filterBindings map (field override string, or false for opt-out); the reloaded widget's re-query targets the authored field / issues no query when opted out", + "evidence": "the persisted metadata + the reloaded widget's re-query trace" + }, + { + "clause": "D1 — the filter bar's Reset affordance is dirty-gated: a pristine bar (every value == its defaultValue) shows NO Reset; changing any filter surfaces a Reset (RotateCcw) that restores all filters to defaults and re-queries the widgets back", + "oracle": "dom", + "verify": "the DashboardFilterBar (data-testid dashboard-filter-bar) has no Reset in the pristine state and one once a value diverges from its default (isDirty); after Reset, values match defaults and widgets re-query — DOM read only after a screenshot confirms the bar rendered", + "evidence": "before/after screenshots + the dirty-vs-pristine DOM" + }, + { + "clause": "D3 — select/lookup filter options render as {value,label}: the trigger shows the selected option's LABEL while the committed value is the option value, and a dataset-sourced option list comes from a SERVER group-by (queryDataset), not a truncated top-N client dedupe (#2578 item 5)", + "oracle": "network", + "verify": "the option-list request is a queryDataset GROUP BY over the source object (complete regardless of row count); the rendered SelectItems carry value/label from resolveDashboardFilterDefs' normalized pairs", + "evidence": "the option-source query trace + the rendered option value/label pairs" } ], "negative": [ "a filter change that repaints without a server re-query (client-side cosmetic filtering) is a FAIL — the network trace is the oracle, not the repaint", - "the opted-out KPI drifting after a filter change is a FAIL (the fixed reference is the point of the opt-out)" + "the opted-out KPI drifting after a filter change is a FAIL (the fixed reference is the point of the opt-out)", + "a filterBinding authored in the inspector that does NOT persist (absent from the re-read metadata) or does NOT drive the widget on reload is a FAIL — a designer that writes into the void is worse than no designer", + "a Reset button visible on a pristine (undirtied) bar, or a select option list silently truncated to the first N rows (missing values that exist), is a FAIL" + ], + "traps": [ + "hydration-race", + "seed-data-thin" ], - "traps": ["hydration-race", "seed-data-thin"], "source": [ "examples/app-showcase/src/ui/dashboards/revenue-pulse.dashboard.ts (framework#2501 / objectui#2578 acceptance fixture)", "examples/app-showcase/src/ui/dashboards/ops-dashboard.dashboard.ts", "examples/app-showcase/src/data/seed/index.ts (sales_region + signed_on seeding, prospects unset by design)", - "examples/app-showcase/src/coverage.ts (dashboard kind notes: revenue-pulse demonstrates dashboard-level filters)" + "examples/app-showcase/src/coverage.ts (dashboard kind notes: revenue-pulse demonstrates dashboard-level filters)", + "objectui packages/app-shell/src/views/metadata-admin/inspectors/DashboardWidgetInspector.tsx (Filter Bindings section — Apply toggle writes false for opt-out, field-override combo, data-testid widget-filter-binding-, patches draft.widgets[i].filterBindings; objectui#2586)", + "objectui packages/plugin-dashboard/src/DashboardFilterBar.tsx (isDirty→Reset affordance RotateCcw, data-testid dashboard-filter-bar; SelectFilter {value,label} options + server GROUP BY via queryDataset, #2578 item 5)", + "objectui packages/core/src/utils/dashboard-filters.ts (resolveDashboardFilterDefs normalizes options to {value,label} pairs)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-07", + "change": "new item: dashboard-level filter re-scoping with per-widget filterBindings, both-sides (bound vs opted-out) verification", + "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 2, + "date": "2026-08-08", + "change": "added the Studio widget-inspector filterBindings AUTHORING round-trip (D2, objectui#2586) plus the D1 dirty→Reset affordance and D3 {value,label} filter-option render clauses", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "dashboards.saved-report-ownership", + "title": "Saved reports are owner-isolated: cross-owner read/run/delete deny as 404, schedules included", + "since": "v15.1", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": [ + "user A (report owner)", + "user B (fresh sign-up, no relation)", + "anonymous" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "two runtime-signed-up users (showcase seeds no sys_saved_report rows and only the admin — create both users and the report in-run)" + ] + }, + "steps": [ + "as A: POST /api/v1/reports (201) and POST /api/v1/reports/:id/schedule; record both ids", + "as B: GET /api/v1/reports/:idA, DELETE /api/v1/reports/:idA, POST /api/v1/reports/:idA/run — capture each", + "as B: GET /api/v1/reports (list) with and without ?ownerId=A — capture row sets", + "as B: GET /api/v1/reports/:idA/schedules and DELETE /api/v1/reports/schedules/:scheduleIdA — capture each (see the known-gap clause)", + "anonymous: GET /api/v1/reports", + "as A afterwards: re-read the report and schedule to prove nothing was destroyed" + ], + "acceptance": [ + { + "clause": "cross-owner GET/DELETE/run all answer 404 REPORT_NOT_FOUND (deny-as-404, anti-enumeration — never 403, never 2xx)", + "oracle": "api", + "verify": "the three responses' status+code per #2980's canAccessReport posture", + "evidence": "the responses" + }, + { + "clause": "the list never leaks: B's listing excludes A's report even with a forged ownerId param; ownerId cannot be spoofed on create either", + "oracle": "api", + "verify": "list bodies + a create-as-B carrying ownerId=A lands owned by B", + "evidence": "listings + the created row's owner" + }, + { + "clause": "anonymous access answers 401 UNAUTHENTICATED", + "oracle": "api", + "verify": "the anonymous GET", + "evidence": "the response" + }, + { + "clause": "KNOWN-GAP PROBE — schedule routes: unscheduleReport and listSchedules currently ignore the caller context (report-service.ts), so B deleting A's schedule succeeds today; the contract this item asserts is deny-as-404, so record the actual outcome and treat a 2xx as a FAIL with a privately-raised finding (do NOT file publicly without maintainer decision — cross-owner destructive access)", + "oracle": "api", + "verify": "the two schedule-route responses + A's schedule surviving (re-read as A)", + "evidence": "responses + the survival read" + }, + { + "clause": "A's artifacts survive every denied attempt byte-identical", + "oracle": "api", + "verify": "final re-reads as A", + "evidence": "the reads" + } + ], + "negative": [ + "any cross-owner 2xx anywhere on /api/v1/reports* is a FAIL; the schedule-route clause documents the one place a FAIL is expected TODAY — a run must not tick it green until the owner check lands" + ], + "traps": [ + "wrong-persona" + ], + "source": [ + "packages/rest/src/rest-route-ledger.ts (reports family)", + "packages/plugins/plugin-reports/src/report-service.ts (canAccessReport #2980; the unchecked unscheduleReport/listSchedules)", + "packages/platform-objects/src/audit/sys-saved-report.object.ts", + "docs/plans/release-15.1-test-plan.md §A10 (#2980/#2981/#2975)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — 15.1 §A10 was never imported; the sweep also surfaced the unchecked schedule routes, recorded here as an expected-FAIL probe", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "dashboards.system-overview-live-counts", + "title": "The built-in System Overview dashboard renders as admin with LIVE sys_* counts, two widget values reconcile against direct API counts, single-environment-only widgets hide gracefully, and no widget renders blank", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": [ + "seeded admin / sysadmin (admin@objectos.ai)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the built-in Setup app + the platform `system_overview` dashboard (packages/platform-objects/src/apps/dashboards/system_overview.dashboard.ts) reached at Setup → System Overview (nav_system_overview, setup-nav.contributions.ts) — present in any booted app, not a showcase fixture", + "the five backing datasets over `sys_*` objects (packages/platform-objects/src/apps/dashboards/system.datasets.ts: sys_user_metrics, sys_session_metrics, sys_organization_metrics, sys_package_installation_metrics, sys_audit_log_metrics)", + "at least one signed-in session (the admin's own) so sys_session count is non-zero, and the seeded sys_user population" + ], + "knownGaps": [ + "in a SINGLE-TENANT / single-environment runtime (the stock standalone serve) two widgets are runtime-gated OFF by design: widget_organizations carries `requiresService: 'org-scoping'` and widget_packages_installed carries `requiresObject: 'sys_package_installation'` (a cloud-only object). Their ABSENCE is the graceful-hide behavior this item checks — NOT a blank tile and NOT a FAIL; a run must not demand those two render" + ] + }, + "steps": [ + "sign in as admin, open the Setup app, and navigate to System Overview (nav_system_overview → dashboard system_overview); let the board settle and screenshot the whole dashboard BEFORE reading any DOM", + "capture the dataset queries the widgets issue on load (the network trace) — each metric tile queries its sys_*_metrics dataset (count aggregate)", + "establish ground truth via the data API: GET/POST a count aggregate on sys_user, and a count aggregate on sys_session, recording both numbers", + "read the rendered value of widget_total_users (Total Users) and widget_active_sessions (Active Sessions) from the DOM (only after the screenshot confirmed render) and compare each to its API count", + "confirm widget_organizations and widget_packages_installed are ABSENT from the rendered board (runtime-gated in single-tenant), not showing a broken/empty tile", + "walk every remaining widget: the three security KPI tiles (login/permission/config event counts over sys_audit_log_metrics), the Audit Events by Action pie, the Events by User bar, and the Audit Events by Action table — confirm each draws a real mark or a named error state, none blank", + "capture the browser console for the whole load" + ], + "acceptance": [ + { + "clause": "widget_total_users shows the live user population — its value equals a direct count aggregate on sys_user, not a hardcoded or stale number", + "oracle": "api", + "verify": "the rendered Total Users tile == COUNT(sys_user) from a direct /api/v1/data aggregate, taken in the same run", + "evidence": "the tile value next to the API count" + }, + { + "clause": "widget_active_sessions shows the live session count — its value equals a direct count aggregate on sys_session (the sys_session_metrics dataset carries NO active-only filter, so the honest oracle is the full sys_session count; record the label-says-'Active' vs dataset-counts-all caveat rather than glossing it)", + "oracle": "api", + "verify": "the rendered Active Sessions tile == COUNT(sys_session) from a direct /api/v1/data aggregate", + "evidence": "the tile value + the API count + the caveat note" + }, + { + "clause": "the reconciliation is authoritative, not cosmetic: the widget's OWN dataset query (on the wire) returns the same count the direct /data aggregate returns — the tile reflects a real query, not a placeholder", + "oracle": "network", + "verify": "the captured sys_user_metrics / sys_session_metrics dataset query results match both the tile and the direct aggregate", + "evidence": "the two dataset-query traces" + }, + { + "clause": "single-environment-only widgets hide gracefully: in a single-tenant runtime widget_organizations (requiresService: org-scoping) and widget_packages_installed (requiresObject: sys_package_installation) are ABSENT, not rendered as blank/error tiles", + "oracle": "screenshot", + "verify": "the board screenshot shows neither gated widget occupying a slot; the runtime-gate source (requiresService/requiresObject on the two widgets) explains the absence", + "evidence": "the board screenshot annotated with the two gated widget ids" + }, + { + "clause": "no widget renders blank: every PRESENT widget draws its mark — metric tiles show numbers, the Audit Events by Action pie shows segments, the Events by User bar shows bars, the table shows rows — or shows a named error state", + "oracle": "screenshot", + "verify": "per-widget screenshot check; DOM mark-count only after the screenshot confirms render (hydration-race guard)", + "evidence": "the per-widget render verdicts" + }, + { + "clause": "the distribution charts reflect the real audit population, not one lonely datapoint: the Audit Events by Action pie/table segment count equals the distinct sys_audit_log.action values present", + "oracle": "api", + "verify": "segment/row count == distinct action count from a direct sys_audit_log group-by aggregate; if only one action is seeded, annotate the single-datapoint weakness rather than counting it as full proof", + "evidence": "the distinct-action aggregate vs the rendered segments" + } + ], + "negative": [ + "a blank widget with no error for any PRESENT widget is a FAIL — the exact blank-tile failure this item exists to catch", + "a runtime-gated widget (organizations / packages) rendering a broken or empty tile INSTEAD of hiding is a FAIL — the gate must remove it, not render it empty", + "a KPI tile whose value does not match the direct API count (a stale cache or a hardcoded number) is a FAIL — the tick 'says what it looks like it says' only if the numbers reconcile", + "running this as a non-admin persona proves nothing about the sysadmin surface (wrong-persona) — and Setup itself must refuse the member" + ], + "traps": [ + "hydration-race", + "single-datapoint", + "seed-data-thin", + "wrong-persona" + ], + "source": [ + "packages/platform-objects/src/apps/dashboards/system_overview.dashboard.ts (widget ids, requiresService/requiresObject gates, globalFilters date range)", + "packages/platform-objects/src/apps/dashboards/system.datasets.ts (the five sys_*_metrics datasets and their count measures)", + "packages/platform-objects/src/apps/setup-nav.contributions.ts (nav_system_overview → dashboard system_overview in the Setup app)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "new item: dashboard-level filter re-scoping with per-widget filterBindings, both-sides (bound vs opted-out) verification", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-08", + "change": "new item: built-in System Overview live-count reconciliation + graceful single-environment widget hiding, grounded in the platform dashboard + sys_*_metrics datasets", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "dashboards.cube-query", + "title": "The showcase_delivery analytics cube serves /api/v1/analytics/*: meta discovers its measures/dimensions, a query answers a known aggregate that reconciles against a direct /data aggregate, and an unwired analytics slot degrades honestly to 404", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "api", + "personas": [ + "seeded admin (admin@objectos.ai)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the showcase_delivery cube (examples/app-showcase/src/data/analytics/showcase.cube.ts — verified present: base table showcase_task; measures count / total_estimate_hours / avg_estimate_hours / done_rate; dimensions status / priority / due_date / assignee), registered as `analyticsCubes` (examples/app-showcase/src/coverage.ts) and served by the `analytics` capability the CLI serve path auto-loads (packages/cli/src/commands/serve.ts CAPABILITY_PROVIDERS.analytics → @objectstack/service-analytics, configKey analyticsCubes)", + "seeded showcase_task rows spanning multiple statuses (the same multi-bucket seed dashboards.chart-first-paint relies on) so the reconciled aggregate has more than one bucket" + ] + }, + "steps": [ + "probe the slot first: GET /api/v1/analytics/meta and record the status — a 404 (handled:false) means the analytics service slot is empty or filled by a self-declared stub; go straight to the degradation clause and record the honest absence", + "GET /api/v1/analytics/meta?cube=showcase_delivery and record the returned cube descriptor", + "establish ground truth: POST /api/v1/data/showcase_task/query with a status group-by count (and a SUM of estimate_hours per status), recording each bucket's count and hour total", + "POST /api/v1/analytics/query { cube: 'showcase_delivery', measures: ['showcase_delivery.count'], dimensions: ['showcase_delivery.status'] } and record rows", + "POST /api/v1/analytics/query { cube: 'showcase_delivery', measures: ['showcase_delivery.total_estimate_hours'], dimensions: ['showcase_delivery.status'] } and record rows", + "reconcile the cube rows against the direct /data aggregate bucket-for-bucket (count and hours)", + "negative: POST /api/v1/analytics/query { cube: 'showcase_delivery', filters: { status: 'done' } } (the off-contract `filters` key instead of `where`) and capture the refusal; also POST the retired { cube, query: {...} } envelope and capture its rejection" + ], + "acceptance": [ + { + "clause": "meta discovers the cube: GET /analytics/meta lists showcase_delivery with exactly its four measures (namespaced showcase_delivery.count / .total_estimate_hours / .avg_estimate_hours / .done_rate) and four dimensions (showcase_delivery.status / .priority / .due_date / .assignee)", + "oracle": "api", + "verify": "the meta response's cubes[] entry for showcase_delivery names all four measures and four dimensions (getMeta keys them `${cube}.${key}` and does NOT filter on the cube's public:false flag)", + "evidence": "the /analytics/meta?cube=showcase_delivery body" + }, + { + "clause": "a known aggregate reconciles: count grouped by status from the cube equals the direct /data showcase_task count-by-status aggregate, bucket-for-bucket", + "oracle": "api", + "verify": "row-by-row equality of showcase_delivery.count-by-status against the /api/v1/data/showcase_task group-by count", + "evidence": "the two aggregates side by side" + }, + { + "clause": "a numeric measure reconciles too, proving the semantic layer is not just counting rows: total_estimate_hours by status equals SUM(estimate_hours) grouped by status from /data", + "oracle": "api", + "verify": "row-by-row equality of showcase_delivery.total_estimate_hours-by-status against the direct SUM aggregate — a divergence would expose a fan-out double-count through the showcase_project join", + "evidence": "the two hour aggregates" + }, + { + "clause": "the query rides the caller's read scope: the /analytics domain forwards the request ExecutionContext (analytics.ts #2852), so results are RLS/tenant-scoped — not run UNSCOPED — verified structurally here (admin reconciles against the same-scoped /data aggregate; the cross-persona proof lives in search.rls-both-personas / access-security)", + "oracle": "api", + "verify": "the admin cube aggregate == the admin /data aggregate; cite handleAnalyticsRequest passing context.executionContext as the mechanism", + "evidence": "the matched aggregates + the mechanism citation" + }, + { + "clause": "DEGRADATION: an empty analytics slot (or a self-declared stub) answers 404 handled:false on BOTH /analytics/query and /analytics/meta — the honest 'install service-analytics' signal (#3891/#4000) — never a 500 and never a silent 200-empty masquerading as an answer", + "oracle": "api", + "verify": "on stock showcase the slot is filled (verify by the served meta above and record this clause as served-not-absent); to exercise the 404 arm, drive an environment without the analytics capability and confirm the 404 rather than a 500/empty-200", + "evidence": "the served-meta status, plus the 404 body if the absent-slot arm is exercised" + }, + { + "clause": "a malformed AnalyticsQuery is rejected AT THE ENTRY with a 400 that names what is wrong — `filters` is told to use `where`, and the retired { query } / { format } envelope carries its migration hint — never forwarded to the engine to die as a `SELECT FROM` SQL error", + "oracle": "api", + "verify": "the `filters` body answers 400 VALIDATION_FAILED naming `where` (analytics.ts assertAnalyticsQueryBody); the { query } envelope is rejected via the retiredKey tombstone (analytics.zod.ts AnalyticsQueryRequestSchema.strict())", + "evidence": "the two refusal bodies" + } + ], + "negative": [ + "cube rows that do NOT reconcile with the direct /data aggregate (a join fan-out double-count, or a wrong grouping) are a FAIL — the semantic layer must agree with the base-table truth", + "a 200 empty-success where the analytics slot is actually absent (instead of the honest 404) is a FAIL — silent degradation is exactly what #3891/#4000 replaced", + "a malformed body reaching the engine and dying as a 500 SQL error instead of a 400 at the entry is a FAIL", + "the route existing only in unit tests but 404-ing on the live server is the dispatcher-vs-hono-route class — the oracle is a live HTTP trace, never a simulated dispatch" + ], + "variants": [ + "measure count (type: count)", + "measure total_estimate_hours (type: sum)", + "measure avg_estimate_hours (type: avg)", + "measure done_rate (type: number, computed CASE expression)" + ], + "traps": [ + "dispatcher-vs-hono-route", + "seed-data-thin", + "single-datapoint" + ], + "source": [ + "examples/app-showcase/src/data/analytics/showcase.cube.ts (the showcase_delivery cube — measures, dimensions, base table, showcase_project join, public:false)", + "examples/app-showcase/src/coverage.ts (analyticsCubes registration → src/data/analytics/showcase.cube.ts, served by /api/v1/analytics/*)", + "packages/runtime/src/domains/analytics.ts (route contract: POST /analytics/query, GET /analytics/meta[?cube], entry validation, ExecutionContext scoping #2852, handled:false 404 for an absent/stub slot #3891/#4000)", + "packages/spec/src/api/analytics.zod.ts (AnalyticsQueryRequestSchema bare shape + retiredKey query/format; meta response cubes[])", + "packages/services/service-analytics/src/analytics-service.ts (getMeta keys measures/dimensions as `${cube}.${key}`, returns all registry cubes)", + "packages/cli/src/commands/serve.ts (CAPABILITY_PROVIDERS.analytics → @objectstack/service-analytics, configKey analyticsCubes)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "new item: showcase_delivery cube /analytics/* meta+query reconciliation against the direct /data aggregate, with the honest empty-slot 404 degradation clause and the entry-validation negatives", + "ref": "claude/platform-test-checklist-ocwugl" + } ] } ] -} +} \ No newline at end of file diff --git a/docs/qa/platform-checklist/areas/i18n.json b/docs/qa/platform-checklist/areas/i18n.json index 9086c9c7f5..d3607078ec 100644 --- a/docs/qa/platform-checklist/areas/i18n.json +++ b/docs/qa/platform-checklist/areas/i18n.json @@ -141,10 +141,10 @@ }, { "id": "i18n.surface-matrix", - "title": "Every translatable surface localizes on a zh-CN session — one pass over the full translation-group vocabulary", + "title": "Every translatable surface localizes on a zh-CN session — one pass over the full translation-group vocabulary, plus the runtime i18n routes it resolves through", "since": "v15", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "browser", "personas": ["admin on a zh-CN session"], @@ -172,6 +172,11 @@ "a Settings namespace — title, group titles, key labels, and the source badges (settings. + settingsCommon.sourceLabels)", "Studio metadata forms (metadataForms group — overlaps i18n.studio-follows-app-locale; a pinned pass there may be cited)", "a sys_ object surface — e.g. the All Users list showing sys_user's translated label (platform-objects-owned bundle)", + "runtime i18n routes (server truth, consulted independently of the browser — these are what the client resolves labels through): GET /api/v1/i18n/locales and record data.locales", + "GET /api/v1/i18n/translations/zh-CN — record whether it is non-empty and whether every key path sits under a declared translation group", + "GET /api/v1/i18n/labels/showcase_task/zh-CN — record its { object, locale, labels } and hold it next to the zh-CN list-header the browser rendered for showcase_task", + "unknown-locale probe: GET /api/v1/i18n/translations/zz-ZZ and GET /api/v1/i18n/labels/showcase_task/zz-ZZ — capture WHICH degradation the running server does (the FileI18nAdapter returns an empty {} map for an unloaded locale; getTranslations does NOT synthesize the default bundle — record the observed shape, do not assume a fallback)", + "feed/audit verb variant (E11): trigger an activity-feed / audit entry (e.g. an assignment or a record edit) and, on the zh-CN session, screenshot its rendered verb/template — the feed/audit verb copy is a translatable surface", "record a per-variant verdict table with the screenshot evidence" ], "acceptance": [ @@ -210,11 +215,43 @@ "oracle": "network", "verify": "the action's request fired and returned 2xx while the zh-CN confirm/success strings were shown — the toast is attached to a real server round-trip", "evidence": "network trace + dialog/toast screenshots" + }, + { + "clause": "GET /i18n/locales lists exactly the configured locale set — data.locales carries the descriptors for the supportedLocales (showcase: en + zh-CN), inside the declared { success, data } envelope (#3636), never a bare array and never a superset", + "oracle": "api", + "verify": "the response body's data.locales equals toLocaleDescriptors(getLocales(), defaultLocale) for the configured locales; unwrapResponse keys on the success flag, so a flag-less body is itself the failure #3636 closed", + "evidence": "the /i18n/locales response" + }, + { + "clause": "GET /i18n/translations/:locale returns a non-empty bundle whose key paths all sit within the declared translation-group vocabulary (translationDataShape) — a key outside the declared groups, or a raw dotted key handed to a consumer, is the silent-strip class", + "oracle": "api", + "verify": "translations for zh-CN is non-empty; spot-check that sampled keys resolve under objects/_views/_actions/_sections/apps.navigation/dashboards/pages/settings groups, not a fourth dialect", + "evidence": "the /translations/zh-CN response + the key check" + }, + { + "clause": "the /labels/:object/:locale route is the resolver the UI reads through, and it agrees with the UI: the labels it returns for showcase_task in zh-CN match the zh-CN list-header labels the browser renders (resolveObjectFieldLabels over the nested objects..fields..label shape — the flat o. dialect that always returned {} was #3778/#3833)", + "oracle": "api", + "verify": "the labels response's field→label map equals the rendered zh-CN list-header row for showcase_task", + "evidence": "the /labels response next to the list-header screenshot" + }, + { + "clause": "an unknown locale degrades honestly and the run records WHICH: /translations/zz-ZZ and /labels/showcase_task/zz-ZZ answer 200 with an empty {} map (the adapter does not synthesize the default bundle at the route), never a 500 and never a dotted-key dump — the observed shape is captured, not assumed", + "oracle": "api", + "verify": "both unknown-locale reads return 200 with an empty translations/labels object (FileI18nAdapter's unloaded-locale path — getTranslations returns {}; fallbackLocale only applies per-KEY inside t(), not to the bulk route); record the exact bodies", + "evidence": "the two unknown-locale responses" + }, + { + "clause": "the activity-feed / audit verb template localizes on the zh-CN session (E11) — the feed/audit entry's verb renders its zh-CN copy, not an English verb and not a raw key", + "oracle": "screenshot", + "verify": "a feed/audit entry's verb/template renders zh-CN on the switched session; a raw key or English verb here is the E11 gap (record which group backs it — the messages/globalActions area is the likely home)", + "evidence": "the feed/audit screenshot" } ], "negative": [ "a raw translation key (dotted path) rendered anywhere is a FAIL — that is the silent-strip failure surfacing to the user", - "a surface whose group the spec declares (e.g. _sections) showing NO translation while the bundle carries one for it is a FAIL against the resolver, not a coverage gap" + "a surface whose group the spec declares (e.g. _sections) showing NO translation while the bundle carries one for it is a FAIL against the resolver, not a coverage gap", + "a /i18n/locales that answers a bare array (the pre-#3636 shape) instead of { success, data: { locales } } is a FAIL — the SDK's unwrapResponse then hands callers the wrong shape depending on which surface mounted the route", + "the /labels route and the rendered UI labels disagreeing for the same object+locale is a FAIL against the resolver — they must be one derivation (#3833), not two copies that drift" ], "variants": [ "nav labels (apps.navigation)", @@ -229,17 +266,22 @@ "settings (settings. + settingsCommon.sourceLabels)", "Studio metadata forms (metadataForms)", "sys_ object labels (platform-objects bundles)", - "relative dates (locale formatting, not a translation group)" + "relative dates (locale formatting, not a translation group)", + "activity-feed / audit verb templates (E11 — a translatable surface not previously in the variant list)" ], - "traps": ["stale-console-bundle", "hydration-race", "wrong-panel"], + "traps": ["stale-console-bundle", "hydration-race", "wrong-panel", "dispatcher-vs-hono-route"], "source": [ "packages/spec/src/system/translation.zod.ts (translationDataShape — the authoritative group vocabulary: objects/_views/_actions/_sections, apps.navigation, messages, globalActions, dashboards, pages, settings, metadataForms, settingsCommon)", "examples/app-showcase/src/system/translations/index.ts (full-column coverage rationale)", + "packages/services/service-i18n/src/i18n-service-plugin.ts (GET /i18n/locales | /translations/:locale | /labels/:object/:locale; { success, data } envelope #3636/#3675; resolveObjectFieldLabels nested shape #3778/#3833; the plugin mount and the dispatcher /i18n domain serve the same routes interchangeably)", + "packages/services/service-i18n/src/file-i18n-adapter.ts (getLocales / getTranslations — unloaded locale → {}; fallbackLocale applies per-KEY in t(), not to the bulk route)", + "packages/services/service-i18n/src/i18n-route-ledger.ts (the three routes, conformance-guarded #3636)", "content/docs/ui/translations.mdx", "scripts/check-i18n-coverage.mjs + scripts/i18n-coverage-baseline.json (frozen-debt ratchet)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "new matrix item: per-surface localization pass over the spec's full translation-group vocabulary, grounded in the showcase bundle and the coverage ratchet", "ref": "claude/platform-test-checklist-ocwugl" } + { "revision": 1, "date": "2026-08-07", "change": "new matrix item: per-surface localization pass over the spec's full translation-group vocabulary, grounded in the showcase bundle and the coverage ratchet", "ref": "claude/platform-test-checklist-ocwugl" }, + { "revision": 2, "date": "2026-08-08", "change": "clause-extension: runtime i18n routes (/i18n/locales configured set, /translations/:locale ⊂ declared vocabulary, /labels/:object/:locale matches the UI, unknown-locale honest degradation), plus the E11 activity-feed/audit verb-localization variant; traps gain dispatcher-vs-hono-route (the plugin mount and dispatcher /i18n domain must answer one shape, #3636/#3833)", "ref": "claude/platform-test-checklist-ocwugl" } ] }, { diff --git a/docs/qa/platform-checklist/areas/identity-auth.json b/docs/qa/platform-checklist/areas/identity-auth.json index 66096a2cd0..1330ec344b 100644 --- a/docs/qa/platform-checklist/areas/identity-auth.json +++ b/docs/qa/platform-checklist/areas/identity-auth.json @@ -150,7 +150,7 @@ "title": "Every supported auth method signs in when enabled, is absent when disabled, and is advertised exactly as configured", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "mixed", "personas": ["anonymous visitor", "provisioned user per method", "admin (for env configuration)"], @@ -162,7 +162,8 @@ ], "knownGaps": [ "magic-link and passkeys: the server flags exist (AuthPluginConfigSchema.magicLink/passkeys) but objectui ships NO login UI for either — advertised-but-unconsumed, tracked in objectui#2514. Run these variants as blocked(dependency, objectui#2514) at the browser lane; the flag-advertisement clause still applies", - "the two spec files disagree on the device-flow paths (packages/spec/src/api/auth-endpoints.zod.ts: /device/request, /device/token, /device/approve vs packages/spec/src/system/auth-config.zod.ts: /device/code, /device/token, /device, /device/approve, /device/deny) — the runner must trust the LIVE server's routes and file the doc divergence if confirmed" + "the two spec files disagree on the device-flow paths (packages/spec/src/api/auth-endpoints.zod.ts: /device/request, /device/token, /device/approve vs packages/spec/src/system/auth-config.zod.ts: /device/code, /device/token, /device, /device/approve, /device/deny) — the LIVE better-auth surface serves POST /device/code, /device/token, /device/approve, /device/deny and GET /device (auth-route-ledger.ts BETTER_AUTH_MOUNTED_SURFACE); trust the live routes and file the doc divergence", + "the password-reset path names ALSO diverge from the spec: AuthEndpointPaths.forgetPassword = '/forget-password' but the live catch-all serves POST /request-password-reset, POST /reset-password and GET /reset-password/:token (BETTER_AUTH_MOUNTED_SURFACE) — trust the live routes (this is the same divergence identity-auth.self-service-password-reset drives)" ] }, "steps": [ @@ -171,8 +172,10 @@ "phone+password: create a phone-carrying user with a known password, POST the phone sign-in (better-auth /sign-in/phone-number surface — 'always works' when the plugin is on per the spec), verify get-session identifies the user", "phone OTP with NO SMS service configured: request /phone-number/send-otp and capture the loud NOT_SUPPORTED rejection (never a silent 200, never a hang)", "SSO/OIDC: with an oidcProviders[] entry configured, verify the login page shows the SSO button (features.sso is refined to 'usable' — ≥1 provider); drive the authorization-code round trip or cite the pinned OIDC dogfood test per rule 6", - "device authorization: with deviceAuthorization on, drive the RFC 8628 flow (request device+user codes, approve in a signed-in browser, poll for the token) against the LIVE server's routes, recording which path spelling the server actually serves", + "device authorization: with deviceAuthorization on, drive the RFC 8628 flow (POST /device/code, approve via /device/approve in a signed-in browser, poll /device/token) against the LIVE server's routes, recording which path spelling the server actually serves", "2FA: with twoFactor on, enable it for a user (sys_user enable_two_factor action, gated on features.twoFactor), sign in, and verify the server-driven challenge (ADR-0069) interrupts before a session is granted", + "discovery documents: GET /.well-known/openid-configuration and GET /.well-known/oauth-authorization-server (both mounted at the APP ROOT, not under /api/v1/auth — RFC 8414 / OIDC require it, auth-plugin.ts) and record the issuer + advertised endpoints", + "self-service identity mutations: POST /api/v1/auth/change-email (authed) and POST /api/v1/auth/delete-user (authed) — capture each, plus the anonymous forge of both", "for each variant: re-boot with the method OFF, then (a) GET /auth/config and confirm the advertisement is gone, (b) screenshot the login page and confirm the affordance is gone, (c) fire the method's endpoint anyway and confirm a server-side refusal", "record the per-variant verdict table (on-side result, off-side result, advertisement parity)" ], @@ -224,12 +227,25 @@ "oracle": "test", "verify": "packages/qa/dogfood/test/oidc-authorization-code-flow.dogfood.test.ts passes on this build", "evidence": "the test output" + }, + { + "clause": "the discovery documents are served and self-consistent: GET /.well-known/openid-configuration and GET /.well-known/oauth-authorization-server each return 200 JSON whose issuer and endpoint URLs point at the actually-mounted base (mounted at app root, not the /api/v1/auth prefix)", + "oracle": "api", + "verify": "both documents parse as JSON; issuer + authorization/token/jwks/userinfo endpoints resolve against the live server (e.g. jwks_uri answers the same key set as GET /api/v1/auth/jwks); a bespoke-issuer boot's issuer matches its configured base", + "evidence": "the two discovery-document responses + the jwks cross-check" + }, + { + "clause": "self-service change-email and delete-user are authed-only mutations: POST /api/v1/auth/change-email and POST /api/v1/auth/delete-user succeed for the signed-in user and are refused for the anonymous forge (better-auth also routes delete-user through GET /api/v1/auth/delete-user/callback confirmation before the row is gone)", + "oracle": "api", + "verify": "the authed change-email returns 2xx (and, if verification is required, does not flip the address until the confirmation link is followed); both endpoints answer 401 to the anonymous forge", + "evidence": "the authed responses + the two anonymous refusals" } ], "negative": [ "a silent 200 on any disabled method's endpoint is a FAIL — a gate that only hides the button is not a gate", "phone OTP hanging or returning 2xx with no SMS service is a FAIL (the spec's own contract is 'loudly NOT_SUPPORTED')", - "ticking magic-link or passkeys as pass at the browser lane is a false positive — there is no UI to drive (objectui#2514); the honest verdict is blocked" + "ticking magic-link or passkeys as pass at the browser lane is a false positive — there is no UI to drive (objectui#2514); the honest verdict is blocked", + "a discovery document whose issuer/endpoints point at a base the server does not actually mount is a FAIL — a wrong .well-known breaks every downstream RP/relying party silently" ], "variants": [ "email+password (POST /api/v1/auth/sign-in/email, /sign-up/email, /sign-out, /get-session)", @@ -247,11 +263,13 @@ "source": [ "packages/spec/src/system/auth-config.zod.ts (AuthPluginConfigSchema: phoneNumber/twoFactor/deviceAuthorization/magicLink/passkeys; socialProviders; oidcProviders; EmailAndPasswordConfigSchema)", "packages/spec/src/api/auth-endpoints.zod.ts (AuthEndpointPaths; AuthFeaturesConfigSchema; device-flow response schemas)", + "packages/plugins/plugin-auth/src/auth-route-ledger.ts (BETTER_AUTH_MOUNTED_SURFACE: the live change-email/delete-user + /.well-known/* rows; auth-plugin.ts mounts the two discovery docs at app root)", "packages/spec/src/kernel/public-auth-features.ts (flag semantics, gated inputs, objectui#2513/#2514 known gaps)", "packages/qa/dogfood/test/oidc-authorization-code-flow.dogfood.test.ts" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "new matrix item: per-method sign-in proof with both-sides gate checks and advertisement parity, grounded in the spec's plugin config + public feature registry", "ref": "claude/platform-test-checklist-ocwugl" } + { "revision": 1, "date": "2026-08-07", "change": "new matrix item: per-method sign-in proof with both-sides gate checks and advertisement parity, grounded in the spec's plugin config + public feature registry", "ref": "claude/platform-test-checklist-ocwugl" }, + { "revision": 2, "date": "2026-08-08", "change": "added the .well-known/openid-configuration + oauth-authorization-server discovery-document clause (issuer/endpoints match the mounted base, jwks cross-check) and self-service change-email + delete-user clauses; recorded the live-route divergences (device flow, password reset) from the spec paths (PENDING-GAPS §D)", "ref": "claude/platform-test-checklist-ocwugl" } ] }, { @@ -259,7 +277,7 @@ "title": "Invitation issuance honors role-scope gates: delegated_admin can invite members but cannot mint admins; a plain member cannot invite at all", "since": "v17", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "mixed", "personas": ["delegated_admin org member", "plain member", "tenant admin (setup only)"], @@ -277,7 +295,8 @@ "immediately read sys_invitation via the API and verify NO row (not even a pending one) was left behind by the refused attempt", "as a plain member: verify the invite affordance is absent in the UI, then fire the invitation endpoint directly and capture the server refusal", "as the invited member address, verify the pending invitation is visible/actionable where the product surfaces it, and check its status vocabulary against the spec enum", - "exercise cancel_invitation (or resend_invitation) on the pending row as the entitled persona and verify the status/state change via the API" + "exercise cancel_invitation (or resend_invitation) on the pending row as the entitled persona and verify the status/state change via the API", + "read the delegable-scope surface that feeds the invite role picker: GET /api/v1/security/my-delegable-scope as the delegated_admin (strictly self-scoped, no target-user parameter — ADR-0090 D12 / ADR-0105 D8) and confirm the returned role set EXCLUDES admin-mintable roles; repeat as a plain member and confirm the scope is empty or the call is denied" ], "acceptance": [ { @@ -315,61 +334,69 @@ "oracle": "api", "verify": "row status is always one of the InvitationStatus enum values; the cancel/resend action's effect is visible in a fresh read", "evidence": "the before/after reads" + }, + { + "clause": "the delegable-scope read is the picker's server truth: GET /api/v1/security/my-delegable-scope returns, for the delegated_admin, exactly the roles that principal may mint (admin-mintable roles absent) — so the UI cannot offer an admin invite it would then be refused for; a plain member's scope is empty or the call is denied", + "oracle": "api", + "verify": "the delegated_admin response's role list contains member but NOT admin; the plain member's response is empty/denied — cross-checked against the admin-role refusal proven above (client method security.describeDelegableScope, rest-route-ledger.ts)", + "evidence": "the two /security/my-delegable-scope responses" } ], "negative": [ "an admin-role invitation that returns success, or that leaves ANY row behind, is a FAIL of privilege-escalation severity — file immediately, P0-verify per RUNNER rule 7", - "UI-only enforcement (affordance hidden but the forged request succeeds) is a FAIL — the server is the authority (ADR-0057 D10)" + "UI-only enforcement (affordance hidden but the forged request succeeds) is a FAIL — the server is the authority (ADR-0057 D10)", + "my-delegable-scope returning admin (or any role the caller cannot actually mint) is a FAIL — the picker would offer an invite the endpoint then refuses, and worse, a client that trusts the scope could try to mint it" ], "automated": { "kind": "e2e", "ref": "packages/qa/dogfood/test/delegated-admin-invite.dogfood.test.ts" }, "traps": ["wrong-persona", "dispatcher-vs-hono-route"], "source": [ "packages/qa/dogfood/test/delegated-admin-invite.dogfood.test.ts (ADR-0105 D8 / #3697; the escalation chain the role cap blocks)", "packages/spec/src/identity/organization.zod.ts (InvitationSchema, InvitationStatus enum)", + "packages/rest/src/rest-route-ledger.ts (GET /api/v1/security/my-delegable-scope — security.describeDelegableScope, ADR-0090 D12 / ADR-0105 D8, self-scoped read half of the delegated-admin gate)", "packages/spec/src/kernel/public-auth-features.ts (organization feature gates sys_invitation invite/cancel/resend actions)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "new item: invitation scope gates and lifecycle, pinned to the delegated-admin-invite dogfood test", "ref": "claude/platform-test-checklist-ocwugl" } + { "revision": 1, "date": "2026-08-07", "change": "new item: invitation scope gates and lifecycle, pinned to the delegated-admin-invite dogfood test", "ref": "claude/platform-test-checklist-ocwugl" }, + { "revision": 2, "date": "2026-08-08", "change": "added GET /api/v1/security/my-delegable-scope clause (delegated_admin scope excludes admin-mintable roles; plain member empty/denied) — the read half that feeds the invite role picker (PENDING-GAPS §D)", "ref": "claude/platform-test-checklist-ocwugl" } ] }, { "id": "identity-auth.admin-lifecycle-operations", - "title": "Admin user-lifecycle operations (ban/unban, set-password, impersonate) enforce, persist, and stay closed to non-admins", + "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": 1, + "revision": 2, "priority": "P1", "surface": "mixed", "personas": ["platform admin", "target user", "non-admin forger"], "fixtures": { "app": "showcase", "requires": [ - "the better-auth admin plugin enabled (plugins.admin: true) — it exposes /admin/ban-user, /admin/unban-user, /admin/set-user-password, /admin/impersonate-user, /admin/stop-impersonating under the auth route and augments sys_user with role/banned/ban_reason/ban_expires and sys_session with impersonated_by", + "the better-auth admin plugin enabled (plugins.admin: true) — it exposes /admin/ban-user, /admin/unban-user, /admin/set-user-password, /admin/impersonate-user, /admin/stop-impersonating, /admin/list-users, /admin/create-user, /admin/set-role, /admin/remove-user, /admin/revoke-user-session(s) under the auth route and augments sys_user with role/banned/ban_reason/ban_expires and sys_session with impersonated_by", "a disposable target user with a known password (create one; do not ban a seeded persona other items depend on)" ] }, "steps": [ "GET /api/v1/auth/config and confirm features.admin is advertised (opt-in flag; it gates the sys_user ban/unban/set-password/impersonate actions)", + "as platform admin, GET /api/v1/auth/admin/list-users and record the roster the admin surface reads from", + "create a login-capable user via POST /api/v1/auth/admin/create-user with an EXPLICIT password AND generatePassword:true set (the console dialog sends both — generatePassword defaults true, the input is labelled 'leave empty to generate'); then sign in as that user with the EXPLICIT password to prove it won over the generated one (#3031/#3033)", "as platform admin, ban the target user with a reason via the sys_user action (or POST /api/v1/auth/admin/ban-user); read sys_user back and record banned/ban_reason", "attempt to sign in as the banned user (POST /api/v1/auth/sign-in/email) and capture the refusal", "unban, then verify the same sign-in now succeeds", "set the target's password via the admin set-password (out-of-band recovery); verify the NEW password signs in and the OLD one is refused", + "sign the target in to establish a LIVE session, then as admin POST /api/v1/auth/admin/revoke-user-sessions for that target; the target's very next authed request (get-session) must 401 mid-flight — the kill is immediate, not deferred to expiry", + "change the target's role via POST /api/v1/auth/admin/set-role and prove the change bites: an operation the new role gates flips outcome (e.g. promote → an admin-only read now 2xx; demote → it now 403)", "impersonate the target from the admin surface; verify via the API that the impersonation session carries impersonated_by, and screenshot the console's impersonation state; stop impersonating and verify the admin's own session is restored", - "as a NON-admin, fire /admin/ban-user directly against the target and capture the refusal", + "POST /api/v1/auth/admin/remove-user for a throwaway user that OWNS at least one showcase row (task/note), then read that owned row back: its owner_id is cleared to null (engine referential-integrity FK clear), the row itself survives, and the owner-anchor transfer guard did NOT veto the cascade (#3023/#3048)", + "as a NON-admin, fire each of /admin/ban-user, /admin/list-users, /admin/create-user, /admin/set-role, /admin/remove-user, /admin/revoke-user-sessions directly and capture every refusal", "run the audit-trail pin and capture its output for the attribution clause" ], "acceptance": [ { - "clause": "ban persists and enforces: sys_user shows banned + ban_reason, and the banned user's sign-in is refused with a named error", - "oracle": "api", - "verify": "the sys_user read after the ban + the refused sign-in response", - "evidence": "row read + refusal" - }, - { - "clause": "unban restores access — the gate is reversible, not a tombstone", + "clause": "ban persists, enforces, and is reversible: after ban, sys_user shows banned + ban_reason and the banned user's sign-in is refused with a named error; after unban, the same credentials sign in again — the gate is reversible, not a tombstone", "oracle": "api", - "verify": "post-unban sign-in succeeds for the same credentials", - "evidence": "the successful sign-in trace" + "verify": "the sys_user read + refused sign-in after the ban, then the successful sign-in after the unban", + "evidence": "row read + the refused sign-in + the post-unban sign-in" }, { "clause": "set-user-password rotates the credential: new password works, old password is refused", @@ -377,6 +404,30 @@ "verify": "both sign-in attempts captured after the rotation", "evidence": "the two responses" }, + { + "clause": "admin create-user mints a login-capable account and explicit-password-wins: a user created with BOTH an explicit password and generatePassword:true signs in with the EXPLICIT password (the generated one was never applied) — and admin/create-user leaves exactly the identity rows it should (sys_user + its credential sys_account)", + "oracle": "api", + "verify": "the create-user response + a successful sign-in with the explicit password; the generated password (never returned to the caller for an explicit request) does not sign in (#3031/#3033, admin-user-endpoints.ts resolvePassword)", + "evidence": "the create response + the two sign-in attempts" + }, + { + "clause": "set-role changes gate outcomes, not just a column: after POST /admin/set-role the target's access to a role-gated operation flips (grants what the new role allows, revokes what it removes) — the role write is authoritative for authorization, verified by re-driving the gated call", + "oracle": "api", + "verify": "the same gated request returns 2xx vs 403 before/after the role change, as the target", + "evidence": "the two gated-request responses bracketing the set-role" + }, + { + "clause": "revoke-user-sessions kills the target's LIVE session mid-flight: a session that answered get-session a moment earlier now 401s immediately after the admin revoke — not at token expiry", + "oracle": "api", + "verify": "get-session as the target: 2xx before the admin revoke, 401 on the very next call after it", + "evidence": "the before/after get-session pair" + }, + { + "clause": "engine cascade exemption (§A5): removing a user who OWNS rows clears owner_id to null on those rows via the engine's referential-integrity FK clear — the owner-anchor transfer guard does NOT veto this system-context cascade write (it rides a server-DERIVED marker, __referentialFieldClear, that cannot be forged from a request), and the owned row survives with owner_id null rather than the delete aborting", + "oracle": "api", + "verify": "read the owned row after remove-user: it exists, owner_id is null; the remove-user call itself returned 2xx (not a guard-abort). Cross-checked by packages/plugins/plugin-security/src/security-plugin.test.ts '[#3023] an engine referential FK clear … is exempt'", + "evidence": "the owned-row read (owner_id null) + the remove-user response" + }, { "clause": "impersonation is attributed: the impersonated session records impersonated_by, and stop-impersonating returns the admin to their own session", "oracle": "api", @@ -396,25 +447,756 @@ "evidence": "the test output" }, { - "clause": "the gate holds both ways: a non-admin's direct /admin/ban-user call is refused server-side (better-auth enforces the platform admin role internally)", + "clause": "the gate holds both ways for EVERY admin operation: a non-admin's direct call to /admin/ban-user, /admin/list-users, /admin/create-user, /admin/set-role, /admin/remove-user and /admin/revoke-user-sessions is each refused server-side (better-auth enforces the platform admin role internally)", "oracle": "api", - "verify": "the forged call returns non-2xx and the target's sys_user row is unchanged", - "evidence": "the refusal + the unchanged row read" + "verify": "each forged call returns non-2xx and the target's sys_user row (and session, and roster) is unchanged", + "evidence": "the six refusals + the unchanged-state reads" } ], "negative": [ "a non-admin forged admin operation succeeding is a FAIL of the highest severity — apply RUNNER rule 7 (independent re-derivation) before acting on it", - "a ban that hides the user in the UI while their sign-in still works is a FAIL — the sign-in refusal is the enforcement, not the list filter" + "a ban that hides the user in the UI while their sign-in still works is a FAIL — the sign-in refusal is the enforcement, not the list filter", + "revoke-user-sessions that only stops NEW logins while the existing live session keeps answering is a FAIL — the contract is an immediate kill", + "remove-user aborting because the owner-anchor guard vetoed the owner_id-null cascade (instead of exempting the engine FK clear) is the #3023 regression returned — FAIL; equally, a create-user that applies the GENERATED password when an explicit one was supplied is the #3031 failure — FAIL" ], "automated": { "kind": "e2e", "ref": "packages/qa/dogfood/test/admin-identity-audit-trail.dogfood.test.ts" }, "traps": ["wrong-persona", "shared-browser-tab"], "source": [ "packages/spec/src/system/auth-config.zod.ts (admin plugin: endpoint list, sys_user role/banned/ban_reason/ban_expires, sys_session.impersonated_by)", + "packages/plugins/plugin-auth/src/auth-route-ledger.ts (BETTER_AUTH_MOUNTED_SURFACE admin/* rows: list-users, create-user, set-role, remove-user, revoke-user-session(s))", + "packages/plugins/plugin-auth/src/admin-user-endpoints.ts (create-user resolvePassword: explicit password wins over generatePassword — #3031/#3033; leaves sys_user + credential sys_account)", + "packages/plugins/plugin-security/src/security-plugin.ts (§A5 #3023 EXEMPTION: __referentialFieldClear owner_id-null cascade rides a server-derived context, the owner-anchor guard must not veto it) + security-plugin.test.ts '[#3023] … engine referential FK clear … is exempt'", "packages/spec/src/kernel/public-auth-features.ts (admin flag gates the sys_user lifecycle actions; SCIM forces it on — ADR-0071)", "packages/qa/dogfood/test/admin-identity-audit-trail.dogfood.test.ts" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "new item: admin lifecycle operations with persistence, enforcement, attribution and both-sides gate checks", "ref": "claude/platform-test-checklist-ocwugl" } + { "revision": 1, "date": "2026-08-07", "change": "new item: admin lifecycle operations with persistence, enforcement, attribution and both-sides gate checks", "ref": "claude/platform-test-checklist-ocwugl" }, + { "revision": 2, "date": "2026-08-08", "change": "added admin/list-users, create-user (explicit-password-wins §E12 #3031/#3033, signs in), set-role (flips gate outcomes), remove-user, revoke-user-sessions (kills live session mid-flight), each non-admin-refused; plus the §A5 engine cascade exemption clause (delete sys_user → owned rows' owner_id set_null; owner-anchor guard does not veto the system-context cascade, #3023/#3048) (PENDING-GAPS §D + §G)", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "identity-auth.session-list-revoke", + "title": "Active-session list is owner-scoped, and each revoke primitive terminates exactly the sessions it names", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["user A (two devices)", "user B (unrelated, second live session)", "platform admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "email+password sign-in enabled (stock showcase) so a user can hold two concurrent sessions from two browser contexts", + "a second unrelated user (B) signed in, to prove the list is owner-scoped (RLS), not a global session dump", + "admin access to the Setup → Sessions surface (nav_sessions, sys_session all_sessions view) for the all-users clause" + ] + }, + "steps": [ + "sign user A in from two independent browser contexts (device-1, device-2), and separately sign user B in once — three live sessions total", + "as A, GET /api/v1/auth/list-sessions (client auth.sessions.list) AND load the Account 'Active Sessions' surface (sys_session `mine` view, filter user_id = {current_user_id}); screenshot the list after it renders", + "cross-check the data-API read: GET /api/v1/data/sys_session as A returns ONLY A's rows — B's session must be invisible (RLS), even though sys_session is a shared managed-by-better-auth table", + "revoke device-2 specifically: POST /api/v1/auth/revoke-session keyed on that session's TOKEN (the object action revoke_session uses recordIdParam:'token' — better-auth keys off the token, not the row id); then issue an authed request from device-2 and capture the 401", + "confirm device-1 is untouched: an authed request from device-1 still 2xx", + "from device-1, invoke revoke-other-sessions (POST /api/v1/auth/revoke-other-sessions, the 'Sign out other devices' toolbar action): every OTHER session for A dies, device-1 survives; re-read list-sessions and confirm exactly one row (device-1) remains", + "as admin, open Setup → Sessions (all_sessions view) and confirm it lists rows across ALL users (A and B both present, user_id column shown), with the revoked_at / revoke_reason fields visible on terminated rows", + "attempt a direct write to sys_session via the data API (PATCH/DELETE /api/v1/data/sys_session/{id}) and capture the refusal — the table is read-only over the data API (apiMethods ['get','list'], writes 405 before 403)" + ], + "acceptance": [ + { + "clause": "the active-session list is owner-scoped: A's list-sessions and the Account sessions view show BOTH of A's sessions and NONE of B's — the shared sys_session table is RLS-filtered to the caller", + "oracle": "api", + "verify": "GET /api/v1/auth/list-sessions and GET /api/v1/data/sys_session as A return A's two rows only; B's row id (known from B's own read) is absent", + "evidence": "A's list responses + B's row id proving the exclusion" + }, + { + "clause": "the browser Active Sessions surface renders the owner's sessions (mine view) — screenshot-confirmed before any DOM read", + "oracle": "screenshot", + "verify": "the Account sessions view shows A's two device rows with ip_address / created_at / expires_at columns; the count matches the API read", + "evidence": "the sessions-list screenshot + API count" + }, + { + "clause": "revoke-session terminates exactly the named session: after revoking device-2 by token, device-2's next request 401s and device-1 keeps working — the revoke is keyed on the token, one session, not all", + "oracle": "api", + "verify": "device-2 authed request returns 401 post-revoke; device-1 authed request still 2xx", + "evidence": "the two post-revoke request traces" + }, + { + "clause": "revoke-other-sessions keeps ONLY the calling session: invoked from device-1, it kills every other session for A and leaves device-1 alive; the post-call list-sessions has exactly one row", + "oracle": "api", + "verify": "list-sessions after the call returns one row (device-1); a request from any previously-other session 401s", + "evidence": "the post-call list + a 401 from a killed session" + }, + { + "clause": "the admin Sessions surface lists ALL users' sessions with revoke metadata: the all_sessions view shows rows for both A and B (user_id column) and surfaces revoked_at / revoke_reason on terminated rows", + "oracle": "screenshot", + "verify": "Setup → Sessions shows cross-user rows; a revoked row shows revoked_at set and a revoke_reason from the {idle_timeout, absolute_max, concurrent_cap, admin} vocabulary", + "evidence": "the admin sessions screenshot + a data-API read of a revoked row" + }, + { + "clause": "sys_session is read-only over the data API — a forged direct write is refused (405 before 403), so revocation only happens through the auth endpoints, never a raw row edit", + "oracle": "api", + "verify": "PATCH/DELETE /api/v1/data/sys_session/{id} returns 405 (method not allowed — apiMethods ['get','list'], identity write guard ADR-0092 D2)", + "evidence": "the forged-write response" + } + ], + "negative": [ + "A's session list returning B's rows (or a global session dump) is an RLS-breach FAIL — the shared table must be owner-filtered", + "revoke-session that signs A out of BOTH devices (killing device-1 too) is a FAIL — it must terminate exactly the named token", + "revoke-other-sessions that also kills the calling session, or that leaves a supposedly-revoked session still answering, is a FAIL", + "a revoked session that keeps answering authed requests until token expiry is a FAIL — revocation is immediate, the 401 is the enforcement not the list filter" + ], + "traps": ["wrong-persona", "shared-browser-tab"], + "source": [ + "packages/plugins/plugin-auth/src/auth-route-ledger.ts (GET list-sessions=auth.sessions.list, POST revoke-session=auth.sessions.revoke, revoke-other-sessions=auth.sessions.revokeOthers, revoke-sessions=auth.sessions.revokeAll)", + "packages/platform-objects/src/identity/sys-session.object.ts (mine view filter user_id={current_user_id}; all_sessions admin view; revoked_at/revoke_reason fields ADR-0069 D4; revoke_session action recordIdParam:'token'; apiMethods ['get','list'] — writes 405 before 403, #1591/ADR-0092 D2)", + "packages/platform-objects/src/apps/setup-nav.contributions.ts (nav_sessions → Setup Sessions, objectName sys_session)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "new item: owner-scoped active-session list (RLS) + per-primitive revoke semantics (revoke-session by token, revoke-other-sessions keeps current) + admin all-sessions view with revoked_at/revoke_reason, grounded in the auth route ledger and sys_session object (PENDING-GAPS §B)", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "identity-auth.api-key-ui-lifecycle", + "title": "Personal API key: minted show-once on Connect-an-Agent, authenticates as its owner, revoke kills it and restore brings it back — mine-view scoped", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["a signed-in member (key owner)", "platform admin", "the key itself (machine caller via x-api-key)"], + "fixtures": { + "app": "showcase", + "requires": [ + "the developer Integrations page (AgentConnectSection) reachable for the signed-in user — POST /api/v1/keys is the mint route (client keys.create) and it is not admin-gated (the key acts as the caller)", + "at least one apiEnabled showcase data object to call with the key (e.g. showcase_task)" + ] + }, + "steps": [ + "as a signed-in member, open the developer Integrations page → 'Connect an AI agent (MCP)' (AgentConnectSection), type a key name and click Generate key; screenshot the show-once dialog", + "capture the mint response: POST /api/v1/keys returns data.key (the raw secret) exactly once, plus id / name / prefix", + "record the raw secret, close the dialog, then re-open the API Keys list (sys_api_key `mine` view) and confirm the row shows only the prefix — the raw key is never shown again (the stored `key` column is hashed + hidden)", + "call a data route as the machine caller: GET /api/v1/data/showcase_task with header x-api-key: (NOT a session cookie/Bearer) and capture the 200 + that the rows returned are the OWNER's row-level-security view (the key acts as the user)", + "revoke the key via the row action revoke_api_key (PATCH /api/v1/data/sys_api_key/{id} bodyExtra {revoked:true}); immediately re-call the data route with x-api-key and capture the 401", + "restore via restore_api_key (PATCH … {revoked:false}); re-call and capture the 200 again — the lifecycle is reversible", + "mine-view scoping: sign in as a DIFFERENT member and confirm the sys_api_key `mine` view (filter user_id={current_user_id}) does NOT list the first user's key; then as admin open Setup → API Keys (the `all_keys` view, nav gated on manage_platform_settings) and confirm every user's key is listed", + "attempt to read the raw secret back via the data API (GET /api/v1/data/sys_api_key/{id}) and confirm the `key` field is absent/hidden — only the prefix is ever returned" + ], + "acceptance": [ + { + "clause": "the secret is shown exactly once: the mint response carries data.key and the show-once dialog displays it, but every subsequent read (list row, get-by-id) returns only the prefix — the stored key is hashed", + "oracle": "api", + "verify": "POST /api/v1/keys response has a raw `key`; a follow-up GET /api/v1/data/sys_api_key/{id} has no raw key, only prefix (the hidden hashed `key` column never serializes)", + "evidence": "the mint response + the follow-up read + the dialog screenshot" + }, + { + "clause": "the key authenticates as its owner: x-api-key on a data route returns 200 and the row set is the OWNER's RLS view — a machine credential, carrying the user's permissions, not a superuser bypass", + "oracle": "api", + "verify": "GET /api/v1/data/showcase_task with x-api-key returns 200; the rows match what the owner sees with a session (not more)", + "evidence": "the x-api-key request trace + a session-read comparison" + }, + { + "clause": "revoke is immediate and enforced server-side: after revoke_api_key the same x-api-key call 401s on the very next request", + "oracle": "api", + "verify": "the post-revoke x-api-key request returns 401", + "evidence": "the revoke PATCH response + the 401" + }, + { + "clause": "restore is reversible: restore_api_key flips revoked back to false and the key authenticates again (200)", + "oracle": "api", + "verify": "the post-restore x-api-key request returns 200", + "evidence": "the restore PATCH + the 200" + }, + { + "clause": "keys are mine-view scoped: the sys_api_key `mine` view lists only the caller's own keys; a second user cannot see the first's, and only the admin all_keys view (manage_platform_settings) shows every user's keys", + "oracle": "api", + "verify": "the second member's mine-view read excludes the first key's id; the admin all_keys read includes it", + "evidence": "the two mine-view reads + the admin read" + }, + { + "clause": "the show-once dialog and the API Keys list render correctly — screenshot-confirmed", + "oracle": "screenshot", + "verify": "the generate dialog shows the copyable secret with the 'not be shown again' warning; the re-opened list shows the prefix-only row", + "evidence": "the two screenshots" + } + ], + "negative": [ + "the raw secret being readable a second time (in a list row, a get-by-id, or a re-opened dialog) is a FAIL — show-once means the hash is all the server keeps", + "a revoked key still returning 200 on a data route is a FAIL — revocation must be enforced at auth, not only hidden in the list", + "an api key that reads MORE than its owner's RLS view (a superuser bypass) is a FAIL — the key carries the user's permissions", + "one user's key appearing in another user's mine view is an RLS FAIL" + ], + "traps": ["wrong-persona", "hydration-race"], + "source": [ + "objectui apps/console/src/pages/developer/AgentConnectSection.tsx (ADR-0036 Phase 2b: POST /api/v1/keys mint, show-once dialog, x-api-key connect steps)", + "packages/runtime/src/route-ledger.ts (POST /keys → client keys.create)", + "packages/platform-objects/src/identity/sys-api-key.object.ts (revoke_api_key/restore_api_key actions PATCH /api/v1/data/sys_api_key/{id} bodyExtra revoked; mine view user_id={current_user_id} vs all_keys; hashed hidden `key`, visible `prefix`; apiMethods ['get','list'])", + "packages/platform-objects/src/apps/setup-nav.contributions.ts (nav_api_keys requiredPermissions ['manage_platform_settings'] — admin all-view)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "new item: personal API key mint (show-once, POST /api/v1/keys) → authenticate-as-owner via x-api-key → revoke 401 → restore 200, plus mine-view vs admin all-view scoping, grounded in AgentConnectSection + sys_api_key object (PENDING-GAPS §B)", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "identity-auth.self-service-profile-password", + "title": "Self-service profile: name + avatar edits persist through real storage, and password change verifies the current password (passwordless users get set-initial)", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["a signed-in email+password user", "a signed-in passwordless (SSO-only) user"], + "fixtures": { + "app": "showcase", + "requires": [ + "the Account app Profile page (objectui apps/console ProfilePage.tsx) reachable for a signed-in user", + "a working file-storage backend so the avatar upload writes a real object (useUpload → presigned/committed file); if storage is unconfigured the avatar clause is blocked(fixture) not faked", + "for the set-initial-password clause: a user with NO local password (signed in via SSO/social) — hasLocalPassword() returns false; on stock showcase this needs a social/OIDC provider, so that clause is blocked(fixture) same class as sso-enforced unless a passwordless user can be provisioned" + ], + "knownGaps": [ + "the set-initial-password branch needs a passwordless (SSO-only) user; stock showcase ships no configured IdP, so provision one or run that clause blocked(fixture)" + ] + }, + "steps": [ + "sign in as the email+password user and open the Account Profile page; screenshot the identity card (name, email, role badge)", + "change the name field and Save (handleSave → auth.updateUser({name}) → POST /api/v1/auth/update-user); re-read GET /api/v1/auth/get-session and confirm the new name; reload the page and confirm the header/avatar-fallback reflect it (persisted, not just local state)", + "upload an avatar (the hidden file input, data-testid profile-avatar-file): useUpload writes the file to storage, then updateUser({image: }); capture the storage write (presigned/commit) and confirm get-session now carries image = that url", + "remove the avatar (profile-avatar-remove-btn → updateUser({image:null})); confirm get-session image is cleared and the fallback initials render again", + "change the password: enter the correct current password + a new one (PasswordCard → auth.changePassword(current,new) → POST /api/v1/auth/change-password); sign out and sign in with the NEW password (2xx), then confirm the OLD password is refused", + "negative: attempt change-password with a WRONG current password and capture the refusal — the current-password check is server-enforced, not just a UI confirm", + "confirm email is immutable on this surface (the email input is disabled, 'Email cannot be changed') — self-service email change is a distinct endpoint (identity-auth.auth-method-matrix covers /auth/change-email)", + "passwordless branch (if a set-initial-capable user exists): as the SSO-only user, the card renders 'Set Local Password' (hasLocalPassword false) — set an initial password (setInitialPassword → no current-password field) and then sign in with email+password to prove the local credential now exists" + ], + "acceptance": [ + { + "clause": "a name change persists server-side: after Save, get-session returns the new name and it survives a reload — updateUser wrote it, the header is not just local React state", + "oracle": "api", + "verify": "GET /api/v1/auth/get-session after the POST /api/v1/auth/update-user shows the new name; a reloaded page shows it too", + "evidence": "the update + get-session responses + the reloaded screenshot" + }, + { + "clause": "avatar upload is a real storage write, not a data: URL: useUpload commits a file object and updateUser({image}) stores its url; get-session reflects the url and the avatar renders", + "oracle": "network", + "verify": "the upload network trace shows a storage presign/commit returning a URL; get-session image equals that URL; the rendered matches", + "evidence": "the upload trace + get-session + avatar screenshot" + }, + { + "clause": "avatar remove clears the image: updateUser({image:null}) empties the stored image and the initials fallback returns", + "oracle": "api", + "verify": "post-remove get-session image is null/empty; the card shows getUserInitials fallback", + "evidence": "the get-session + the screenshot" + }, + { + "clause": "password change verifies the CURRENT password and rotates the credential: the correct current password + new one succeeds, the new password then signs in and the old one is refused", + "oracle": "api", + "verify": "change-password 2xx; sign-in with new password 2xx; sign-in with old password non-2xx", + "evidence": "the change response + the two sign-in attempts" + }, + { + "clause": "a wrong current password is refused server-side — the verification is real, not a client-only confirm", + "oracle": "api", + "verify": "change-password with an incorrect current password returns non-2xx and the credential is unchanged (old password still signs in)", + "evidence": "the refusal + a subsequent old-password sign-in still working" + }, + { + "clause": "a passwordless user gets set-initial-password, not change-password: with hasLocalPassword false the card omits the current-password field, setInitialPassword creates the local credential, and email+password sign-in then works", + "oracle": "api", + "verify": "the set-initial call 2xx with no current-password; a subsequent email+password sign-in for that user 2xx (was impossible before)", + "evidence": "the set-initial response + the new sign-in" + } + ], + "negative": [ + "a name/avatar 'save' that updates the header but does NOT persist (gone after reload) is a FAIL — updateUser must write server-side", + "an avatar stored as an inline data: URL rather than a committed storage object is a FAIL (the item asserts a real storage write)", + "change-password succeeding with a WRONG current password is a security FAIL — the current-password check must be server-enforced", + "offering the passwordless user a change-password form with a required current password they don't have (locking them out of setting one) is a FAIL — set-initial is the correct branch" + ], + "traps": ["hydration-race", "stale-console-bundle"], + "source": [ + "objectui apps/console/src/pages/system/ProfilePage.tsx (updateUser name/image; useUpload avatar; PasswordCard changePassword vs setInitialPassword gated on hasLocalPassword; email immutable; data-testids profile-avatar-file/-upload-btn/-remove-btn)", + "packages/plugins/plugin-auth/src/auth-route-ledger.ts (POST /api/v1/auth/update-user=auth.updateUser, POST /api/v1/auth/change-password=auth.changePassword, GET /api/v1/auth/get-session=auth.me)", + "packages/platform-objects/src/identity/sys-account.object.ts (previous_password_hashes ring — ADR-0069 D1 reuse-prevention backs change-password)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "new item: self-service name + avatar (real storage write, persists) and password change with current-password verification, plus the passwordless set-initial branch, grounded in ProfilePage.tsx + the auth route ledger (PENDING-GAPS §B)", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "identity-auth.org-membership-team-management", + "title": "Setup Organization page resolves the active org and drives member/invitation/team management through the better-auth org endpoints — non-admins refused server-side", + "since": "v17", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["tenant admin / org owner", "a target org member", "a non-admin org member (forger)"], + "fixtures": { + "app": "showcase", + "requires": [ + "the organization capability mounted (always mounted per ADR-0081 D1; plugin-auth's default-org bootstrap guarantees an org to manage) and a session with an active organization so {current_org_id} resolves", + "at least two members in the org so role changes and removals have a target that is not the admin" + ] + }, + "steps": [ + "sign in as the org owner/admin and open Setup → People & Org → Organization (nav_organization: type object, objectName sys_organization, recordId {current_org_id}, ADR-0081); screenshot the org record page and confirm {current_org_id} resolved to the session's active org (not the list fallback)", + "confirm the record page exposes the Members / Invitations / Teams tabs with the better-auth row actions (GET list-members, list-invitations, list-teams feed them)", + "change a member's role: POST /api/v1/auth/organization/update-member-role (client organizations.updateMemberRole) to one of the 4-name vocabulary {owner, admin, member, guest}; read the membership back and confirm the new role", + "prove the role change bites: an operation the new role gates flips outcome for that member (e.g. promote to admin → an org-admin-only action now permitted; demote → refused)", + "rename the organization: POST /api/v1/auth/organization/update (organizations.update) with a new name; re-read GET get-full-organization and confirm the rename persisted and the nav label follows", + "remove a member: POST /api/v1/auth/organization/remove-member (organizations.removeMember); confirm the member's org-scoped access SHRINKS — a resource they could read as a member now refuses", + "create a team + add members: POST /api/v1/auth/organization/create-team (organizations.teams.create), then POST /api/v1/auth/organization/add-team-member (organizations.teams.addMember) for two users; read sys_team_member and confirm two join rows exist (unique on team_id+user_id)", + "both-sides gate: as a NON-admin member, fire update-member-role / remove-member / update / create-team / add-team-member directly and capture each server refusal; screenshot that the management affordances are absent in the non-admin's UI" + ], + "acceptance": [ + { + "clause": "{current_org_id} resolves to the session's active organization: the Organization nav opens that org's record page (Members/Invitations/Teams tabs), not the raw sys_organization list — the ADR-0081 active-org token is wired", + "oracle": "screenshot", + "verify": "the org record page renders for the active org id (cross-checked against GET /api/v1/auth/organization/get-active-member / get-full-organization); the three management tabs are present", + "evidence": "the org-page screenshot + the get-active/get-full response" + }, + { + "clause": "update-member-role writes a role from the 4-name vocabulary and it bites: the membership read shows the new role (one of owner/admin/member/guest) and a role-gated operation flips outcome accordingly", + "oracle": "api", + "verify": "GET list-members after update-member-role shows the new role; the same gated request returns 2xx vs 403 before/after for that member", + "evidence": "the membership read + the bracketing gated requests" + }, + { + "clause": "rename via organization/update persists: get-full-organization returns the new name and the surface follows", + "oracle": "api", + "verify": "GET get-full-organization after the update shows the renamed org", + "evidence": "the before/after org reads" + }, + { + "clause": "remove-member shrinks access: after removal the ex-member is refused a resource they could reach as a member — removal is an authorization change, not just a roster edit", + "oracle": "api", + "verify": "an org-scoped request that succeeded for the member returns a refusal after remove-member", + "evidence": "the before/after member requests" + }, + { + "clause": "create-team + add-team-member land real join rows: sys_team_member has one row per (team, user), created through the better-auth org endpoints (generic CRUD on the managed table is suppressed)", + "oracle": "api", + "verify": "GET /api/v1/data/sys_team_member (or list-team-members) shows the two membership rows for the new team; the (team_id,user_id) pairs match", + "evidence": "the sys_team_member read" + }, + { + "clause": "the gate holds both ways: a non-admin's direct update-member-role / remove-member / update / create-team / add-team-member calls are each refused server-side, and the management affordances are absent in the non-admin UI", + "oracle": "api", + "verify": "each forged non-admin call returns non-2xx and leaves membership/org/team state unchanged; the non-admin org page shows no management actions", + "evidence": "the forged-request responses + the non-admin screenshot" + } + ], + "negative": [ + "an org management surface where the affordance is hidden but the forged endpoint succeeds for a non-admin is a FAIL — the server is the authority (ADR-0057 D10)", + "remove-member that drops the roster row but leaves the ex-member's org-scoped access intact is a FAIL — removal must change authorization", + "a role written outside the {owner, admin, member, guest} vocabulary, or a role change that does not flip any gate, is a FAIL", + "the Organization nav landing on the raw sys_organization list because {current_org_id} did not resolve (when an active org exists) is a FAIL of the ADR-0081 wiring" + ], + "traps": ["wrong-persona", "dispatcher-vs-hono-route", "hydration-race"], + "source": [ + "packages/platform-objects/src/apps/setup-nav.contributions.ts (nav_organization recordId {current_org_id}, ADR-0081; Teams/Invitations always mounted per ADR-0081 D1)", + "packages/plugins/plugin-auth/src/auth-route-ledger.ts (organization family: update-member-role, remove-member, update, create-team, add-team-member, list-members/teams/invitations, get-active-member, get-full-organization)", + "packages/spec/src/identity/organization.zod.ts (MemberSchema role vocabulary: owner/admin/member/guest)", + "packages/platform-objects/src/identity/sys-team-member.object.ts (add_team_member/remove_team_member actions → organization/add-team-member; unique team_id+user_id; requiresFeature organization)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "new item: Setup Organization page {current_org_id} resolution (ADR-0081) with Members/Invitations/Teams tabs, update-member-role (4-name vocab)/remove-member/rename, create-team + add-team-member → sys_team_member rows, non-admin refused server-side (PENDING-GAPS §B). Teams membership deep-tested in identity-auth.teams-bu-membership; org-member management stays here", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "identity-auth.teams-bu-membership", + "title": "Teams and the Business Unit tree: memberships land real rows, and a BU placement widens/narrows a scoped persona's read along the tree", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": ["org admin", "two members to place on a team", "a scope-limited persona whose read follows the BU tree"], + "fixtures": { + "app": "showcase", + "requires": [ + "the organization capability mounted (for the team half — sys_team / sys_team_member via the better-auth org endpoints)", + "the sys_business_unit tree available (managedBy 'platform' — writable over the data API, unlike the better-auth identity tables) with at least a root company node to parent a child under", + "for the scope-geometry clause: a sharing/scope configuration that actually consumes the BU tree (recipient_type business_unit sharing rules, or a scope-depth persona) — if no showcase geometry consumes BU membership, run that clause blocked(fixture) and record it" + ], + "knownGaps": [ + "whether a stock showcase persona's read is scoped BY the business-unit tree depends on the seeded sharing/scope config; if none consumes it, the tree-widening clause is blocked(fixture) — the team-membership and BU-placement clauses still run" + ] + }, + "steps": [ + "create a team via POST /api/v1/auth/organization/create-team (or the sys_team create_team action) and add two members via POST /api/v1/auth/organization/add-team-member; read sys_team_member and confirm two rows (unique team_id+user_id)", + "remove one via remove-team-member and confirm the join row is gone (the endpoint keys on the (teamId,userId) pair, not the row id)", + "create a CHILD sys_business_unit under an existing root: POST /api/v1/data/sys_business_unit with kind (company|division|department|office|cost_center) and parent_business_unit_id = the root's id; confirm it appears in the Org Chart tree view under its parent", + "place a user in the child BU: create a sys_business_unit_member row (business_unit_id, user_id, function_in_business_unit member|lead|deputy, is_primary); confirm the placement via a data-API read", + "if BU scope geometry is configured: as the scope-limited persona, record the row set visible BEFORE the placement, then place the persona (or a record they can see) into the child BU and re-read — the visible set should WIDEN or NARROW along the tree per the geometry (cross-ref identity-auth via access-security.scope-depth-asymmetry which owns the depth matrix)", + "move the child BU to a different parent (re-parent parent_business_unit_id) and, if geometry consumes it, re-read the scoped persona's rows to confirm the read follows the new tree position", + "negative: attempt the team mutations and the BU writes as a non-admin and capture the refusals" + ], + "acceptance": [ + { + "clause": "team membership lands real rows: create-team + add-team-member produce exactly one sys_team_member per (team,user), and remove-team-member deletes exactly that pair", + "oracle": "api", + "verify": "sys_team_member reads before/after each mutation; the (team_id,user_id) rows match the two added members and the removal drops exactly one", + "evidence": "the sys_team_member reads" + }, + { + "clause": "a child business unit attaches to the tree: the new sys_business_unit carries parent_business_unit_id = the root and renders under it in the Org Chart tree view", + "oracle": "api", + "verify": "GET /api/v1/data/sys_business_unit for the child shows the parent id; the tree view (org_chart) renders it nested (screenshot after render)", + "evidence": "the BU read + the org-chart screenshot" + }, + { + "clause": "a user placement is a real sys_business_unit_member row with its function/primary attributes", + "oracle": "api", + "verify": "the sys_business_unit_member read shows business_unit_id + user_id + function_in_business_unit + is_primary", + "evidence": "the membership read" + }, + { + "clause": "when scope geometry consumes the BU tree, a placement changes a scoped persona's visible rows along the tree — widening (placed higher / into a parent that expands subordinates) or narrowing accordingly; re-parenting moves the read with it", + "oracle": "api", + "verify": "the scoped persona's row set before vs after the placement/re-parent differs exactly by the subtree the geometry expands; if no geometry consumes BU membership this clause is blocked(fixture) and recorded", + "evidence": "the before/after scoped reads (or the recorded block)" + }, + { + "clause": "team and BU mutations are admin-gated: a non-admin's create-team/add-team-member and BU writes are refused server-side", + "oracle": "api", + "verify": "the forged non-admin calls return non-2xx and leave sys_team_member / sys_business_unit(_member) unchanged", + "evidence": "the refusals + the unchanged reads" + } + ], + "negative": [ + "add-team-member that does not create a sys_team_member row (or creates duplicates past the unique team_id+user_id constraint) is a FAIL", + "a BU placement that the scope geometry claims to consume but which does NOT move the scoped persona's read is a FAIL — the tree must be load-bearing, not decorative", + "silently degrading a BU scope to own/org when an intermediate depth is authored is the ADR-0049 loud-degradation concern — record it (see PENDING-GAPS §H five-depth note), do not tick it green" + ], + "traps": ["wrong-persona", "seed-data-thin", "hydration-race"], + "source": [ + "packages/platform-objects/src/identity/sys-team-member.object.ts (add_team_member/remove_team_member → organization/add-team-member|remove-team-member; unique team_id+user_id)", + "packages/platform-objects/src/identity/sys-business-unit.object.ts (canonical BU tree ADR-0057 D2; kind enum; parent_business_unit_id self-ref; org_chart tree view; managedBy 'platform' — writable over the data API)", + "packages/platform-objects/src/identity/sys-business-unit-member.object.ts (user↔BU placement: function_in_business_unit member/lead/deputy, is_primary)", + "docs/qa/platform-checklist/areas/access-security.json (access-security.scope-depth-asymmetry — the depth matrix this cross-references for the tree-widening geometry)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "new item: team membership rows (create-team/add/remove) + child business-unit creation and user placement on the sys_business_unit tree, with a scope-geometry-consumes-the-tree clause cross-referencing access-security.scope-depth-asymmetry (PENDING-GAPS §C). Org-member management lives in identity-auth.org-membership-team-management", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "identity-auth.self-service-password-reset", + "title": "Forgot-password: request → token captured at the dev mail transport → reset; old password refused, expired/reused token refused loudly", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": ["anonymous visitor (holds no session)", "the account owner (before and after reset)"], + "fixtures": { + "app": "showcase", + "requires": [ + "email+password sign-in enabled and an email transport configured — for a hermetic run use the dev `log` mail transport (EMAIL_TRANSPORT_PROVIDERS 'log' → LogTransport) so the reset email (and its token) is captured in the transport output rather than actually sent", + "a target user with a known current password" + ], + "knownGaps": [ + "capturing the reset TOKEN requires reading it from the dev mail transport (LogTransport) output — if the deployment sends via a real provider with no capture hook, the token-capture step is blocked(fixture); the request/refusal clauses that don't need the token still run" + ] + }, + "steps": [ + "as anonymous, request a reset: POST /api/v1/auth/request-password-reset with the target email (NOTE the live route is request-password-reset / reset-password — the spec's AuthEndpointPaths says /forget-password, a divergence; trust the live server per RUNNER, and file the doc divergence)", + "capture the reset email at the dev mail transport (LogTransport) and extract the token (the link is GET /api/v1/auth/reset-password/:token)", + "complete the reset: POST /api/v1/auth/reset-password with the token + a new password; capture the response", + "sign in with the NEW password (2xx) and confirm the OLD password is now refused", + "reuse the SAME token a second time (POST /api/v1/auth/reset-password again) and capture the loud refusal — a consumed token must not reset again", + "request a fresh reset, let the token expire (or use a tampered/garbage token) and confirm the reset is refused with a named error, never a silent success", + "negative: request-password-reset for an unknown email should not leak whether the account exists (anti-enumeration) — record the response shape", + "confirm password-reuse prevention if configured: resetting to the OLD password is refused where password_history_count > 0 (previous_password_hashes ring, ADR-0069 D1)" + ], + "acceptance": [ + { + "clause": "the request issues a real reset artifact: POST /api/v1/auth/request-password-reset produces an email at the dev transport carrying a reset token/link (GET /api/v1/auth/reset-password/:token)", + "oracle": "log", + "verify": "the LogTransport output for the request contains the reset link with a token; the request response is a non-leaking 2xx/accepted", + "evidence": "the captured transport output" + }, + { + "clause": "the token completes the reset and rotates the credential: POST /api/v1/auth/reset-password with the token + new password succeeds, the new password signs in and the old one is refused", + "oracle": "api", + "verify": "reset 2xx; sign-in new 2xx; sign-in old non-2xx", + "evidence": "the reset response + the two sign-in attempts" + }, + { + "clause": "a consumed token cannot be reused: a second reset with the same token is refused with a named error", + "oracle": "api", + "verify": "the second reset-password returns non-2xx (token already used)", + "evidence": "the second-attempt response" + }, + { + "clause": "an expired or tampered token is refused LOUDLY — never a silent success that leaves the password unchanged while reporting OK", + "oracle": "api", + "verify": "reset with an expired/garbage token returns a named non-2xx and the credential is unchanged (old password still signs in)", + "evidence": "the refusal + a subsequent old-password sign-in" + }, + { + "clause": "request does not leak account existence: request-password-reset for an unknown email returns the same non-committal shape as for a known one (anti-enumeration)", + "oracle": "api", + "verify": "the known-email and unknown-email request responses are indistinguishable in status/body", + "evidence": "the two request responses" + } + ], + "negative": [ + "a reset that returns 2xx but leaves the password unchanged (token not actually honored) is a FAIL — the new password must sign in and the old must not", + "a reused or expired token that still resets the password is a security FAIL — tokens are single-use and time-bounded", + "request-password-reset returning a DIFFERENT response for known vs unknown emails is an account-enumeration FAIL", + "a silent success on a garbage token (no error, no change) is a FAIL — the spec's contract is a loud refusal" + ], + "traps": ["dispatcher-vs-hono-route", "wrong-persona"], + "source": [ + "packages/plugins/plugin-auth/src/auth-route-ledger.ts (BETTER_AUTH_MOUNTED_SURFACE: POST /api/v1/auth/request-password-reset, POST /api/v1/auth/reset-password, GET /api/v1/auth/reset-password/:token — the LIVE routes; AuthEndpointPaths.forgetPassword='/forget-password' is the divergent spec name)", + "packages/plugins/plugin-email/src/transports/index.ts (EMAIL_TRANSPORT_PROVIDERS 'log' → LogTransport — the dev capture transport)", + "packages/platform-objects/src/identity/sys-account.object.ts (previous_password_hashes ring — ADR-0069 D1 reuse-prevention)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "new item: forgot-password round trip (request-password-reset → dev-transport token capture → reset-password), old-password-refused/new-works, single-use + expiry refusals, anti-enumeration, with the token-capture dev-mail dependency recorded as a knownGap (PENDING-GAPS §B)", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "identity-auth.oauth-app-consent-loop", + "title": "OAuth provider: register an app (secret shown once), run the authorization-code consent loop — approve mints tokens + a consent record, deny mints none", + "since": "v17", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": ["an org admin registering the OAuth client", "the resource-owner user granting/denying consent"], + "fixtures": { + "app": "showcase", + "requires": [ + "the better-auth oidcProvider plugin configured (the oauth2/* routes are gated: auth-route-ledger 'requires: oidcProvider') — stock showcase ships no configured OAuth provider flow, so this item is blocked(fixture) until one is provisioned", + "a registered client + a redirect URI to complete the authorization-code round trip" + ], + "knownGaps": [ + "sys_oauth_consent is apiEnabled:false (apiMethods []) — the consent ROW is not readable over the data API; verify consent via the auth surface GET /api/v1/auth/oauth2/get-consents (the row's presence implies consent for the listed scopes; the old consent_given boolean was removed)" + ] + }, + "blocked": { "by": "fixture", "ref": "no stock showcase oidcProvider flow — needs a configured OAuth provider + client, same fixture class as identity-auth.sso-enforced-first-paint / linked-accounts-social" }, + "steps": [ + "as admin, register an OAuth client: POST /api/v1/auth/oauth2/create-client (client oauth.applications.register, requires oidcProvider); capture the response and the client_secret — revealed ONCE at registration", + "re-read the client via GET /api/v1/auth/oauth2/get-client and confirm the secret is NOT returned again (only client_id / public metadata)", + "begin the authorization-code flow: GET /api/v1/auth/oauth2/authorize with the client_id, redirect_uri, scope and state; as the resource owner, land on the consent page and screenshot the requested-scopes list", + "APPROVE: POST /api/v1/auth/oauth2/consent (oauth.consent) accept; follow the redirect, exchange the code at POST /api/v1/auth/oauth2/token, and capture the issued access/refresh tokens", + "confirm a consent record now exists: GET /api/v1/auth/oauth2/get-consents shows a consent for this client covering the approved scopes (sys_oauth_consent row — not data-API readable)", + "run the flow again for the SAME client+scopes and confirm the consent screen is SKIPPED (the recorded consent short-circuits it)", + "DENY path: start a fresh authorize with an added scope (forcing consent), deny it, and confirm NO tokens are issued and NO new consent is recorded", + "mine-view scoping: confirm the consent surface shows the CALLER's consents only — another user cannot see this user's oauth2 consents" + ], + "acceptance": [ + { + "clause": "the client_secret is revealed exactly once at registration: create-client returns it, and get-client afterwards returns only public metadata (no secret)", + "oracle": "api", + "verify": "the create-client response carries client_secret; the follow-up get-client does not", + "evidence": "the create + get responses" + }, + { + "clause": "the consent page lists the requested scopes before any token is minted — the user sees what they are authorizing", + "oracle": "screenshot", + "verify": "the consent screen enumerates the scopes from the authorize request", + "evidence": "the consent screenshot" + }, + { + "clause": "approve mints tokens AND records consent: the token exchange returns access/refresh tokens and get-consents shows a consent covering the approved scopes for this client", + "oracle": "api", + "verify": "POST oauth2/token returns tokens; GET oauth2/get-consents lists the client+scopes (the sys_oauth_consent row's existence is the consent — no consent_given flag)", + "evidence": "the token response + the get-consents read" + }, + { + "clause": "a recorded consent short-circuits the screen: a repeat authorize for the same client+scopes skips consent and proceeds to the redirect", + "oracle": "network", + "verify": "the second authorize does not render the consent page; it redirects with a code directly", + "evidence": "the second-flow trace" + }, + { + "clause": "deny mints nothing: denying consent issues no tokens and records no consent — the deny path is a clean no-op on credentials", + "oracle": "api", + "verify": "no token is exchanged after deny; get-consents shows no new consent for the denied scope set", + "evidence": "the deny trace + the get-consents read" + }, + { + "clause": "consents are mine-view scoped: get-consents returns only the caller's own consents", + "oracle": "api", + "verify": "a second user's get-consents does not include this user's consent", + "evidence": "the two get-consents reads" + } + ], + "negative": [ + "the client_secret being retrievable after registration (via get-client or the data API) is a FAIL — it is a show-once credential", + "approve that mints tokens but records NO consent (so the screen re-prompts forever) — or deny that still mints tokens — is a FAIL", + "one user seeing another user's oauth2 consents is a scoping FAIL" + ], + "traps": ["dispatcher-vs-hono-route", "wrong-persona", "hydration-race"], + "source": [ + "packages/plugins/plugin-auth/src/auth-route-ledger.ts (oauth-provider family, requires oidcProvider: oauth2/create-client=oauth.applications.register, get-client, consent=oauth.consent, get-consents, oauth2/authorize, oauth2/token)", + "packages/platform-objects/src/identity/sys-oauth-consent.object.ts (row implies consent for listed scopes — consent_given removed; apiEnabled:false so verify via get-consents, not the data API)", + "packages/platform-objects/src/identity/sys-oauth-application.object.ts + setup-nav.contributions.ts (nav_oauth_apps → Setup OAuth Applications)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "new item: OAuth client registration (secret once) + authorization-code consent loop (approve mints tokens + consent record, deny mints none, recorded consent short-circuits), mine-view scoped; blocked(fixture) pending a configured oidcProvider flow (PENDING-GAPS §C)", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "identity-auth.linked-accounts-social", + "title": "Linked accounts: link a social/OIDC identity through the redirect round-trip → a sys_account row appears in mine-view; unlink removes it; provider-less boot degrades honestly", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": ["a signed-in user linking a second identity", "the same user after unlinking"], + "fixtures": { + "app": "showcase", + "requires": [ + "at least one social/OIDC provider configured (socialProviders or oidcProviders) so link_social has a provider to dance with — stock showcase ships none, so this item is blocked(fixture) until a provider is configured", + "the Account 'Identity Links' surface (nav_accounts → sys_account) reachable" + ] + }, + "blocked": { "by": "fixture", "ref": "no stock showcase social/OIDC IdP — link_social needs a configured provider, same fixture class as identity-auth.sso-enforced-first-paint / oauth-app-consent-loop" }, + "steps": [ + "as a signed-in user, open Account → Identity Links (sys_account `mine` view, filter user_id={current_user_id}); screenshot the initial link set", + "invoke link_social for a configured provider: the action is type:'url' — GET /api/v1/auth/sign-in/social?provider=

&callbackURL=/_console/apps/account/sys_account (full-page navigation, NOT XHR, so the OAuth 302 dance and link cookie work); complete the provider round-trip and land back on the Identity Links view", + "confirm a sys_account row now exists for that provider in the caller's mine-view (provider_id = the provider, user_id = the caller, issuer stamped)", + "unlink it: the unlink_account action → POST /api/v1/auth/unlink-account with accountId = the sys_account ROW id (better-auth 1.7 keys on the row id); confirm the row is gone from mine-view", + "both-sides / degradation: on a boot with NO provider configured, confirm link_social degrades honestly — the affordance is absent or names the missing provider, rather than offering a link that dead-ends (open-edition honest-degradation posture)", + "confirm sys_account is read-only over the data API — a forged direct insert/delete is refused (apiMethods ['get','list'], writes 405)" + ], + "acceptance": [ + { + "clause": "linking a social identity creates a sys_account row in the caller's mine-view after the redirect round-trip completes", + "oracle": "api", + "verify": "GET /api/v1/data/sys_account (mine view) after the link shows a new row with provider_id = the provider and user_id = the caller; issuer is stamped", + "evidence": "the post-link mine-view read" + }, + { + "clause": "the link surface renders the caller's own links only (mine-view scoped) — screenshot-confirmed", + "oracle": "screenshot", + "verify": "Identity Links shows the caller's provider rows; a different user's links are not present", + "evidence": "the Identity Links screenshot" + }, + { + "clause": "unlink removes the row: unlink_account keyed on the sys_account row id deletes exactly that link from mine-view", + "oracle": "api", + "verify": "POST /api/v1/auth/unlink-account with the row's accountId; a follow-up mine-view read no longer contains it", + "evidence": "the unlink response + the follow-up read" + }, + { + "clause": "a provider-less boot degrades honestly: link_social is absent or names the missing provider — never a link affordance that dead-ends", + "oracle": "screenshot", + "verify": "with no provider configured, the Identity Links surface shows no dead link action (or an explicit unavailable state)", + "evidence": "the provider-less screenshot" + }, + { + "clause": "sys_account is read-only over the data API — links are mutated only through the auth endpoints, not raw row writes", + "oracle": "api", + "verify": "a forged POST/DELETE to /api/v1/data/sys_account returns 405 (apiMethods ['get','list'], identity write guard ADR-0092 D2)", + "evidence": "the forged-write response" + } + ], + "negative": [ + "a link that appears in the UI but does not create a sys_account row (client-only) is a FAIL — the row is the durable identity link", + "unlink that hides the row from the list but leaves the sys_account (so the provider still signs the user in) is a FAIL", + "one user's identity links appearing in another's mine-view is an RLS FAIL", + "a provider-less boot offering a link_social action that navigates to a dead endpoint is a dishonest-degradation FAIL" + ], + "traps": ["wrong-persona", "dispatcher-vs-hono-route", "hydration-race"], + "source": [ + "packages/platform-objects/src/identity/sys-account.object.ts (link_social type:'url' → /api/v1/auth/sign-in/social?provider=&callbackURL=; unlink_account → /api/v1/auth/unlink-account accountId=row id; mine view user_id={current_user_id} vs all_links; provider options; apiMethods ['get','list'])", + "packages/plugins/plugin-auth/src/auth-route-ledger.ts (POST link-social=auth.accounts.linkSocial, GET list-accounts=auth.accounts.list, POST unlink-account=auth.accounts.unlink)", + "packages/platform-objects/src/apps/setup-nav.contributions.ts (nav_accounts → 'Identity Links', objectName sys_account)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "new item: social/OIDC account linking round-trip → sys_account mine-view row, unlink removal, provider-less honest degradation; blocked(fixture) pending a configured IdP (PENDING-GAPS §C)", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "identity-auth.identity-import-wizard", + "title": "Admin CSV identity import: password-policy auto/temporary drive per-row credentials, imported users sign in, non-admins denied", + "since": "v17", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": ["platform admin (running the import)", "an imported user (signing in afterwards)", "a non-admin (forger)"], + "fixtures": { + "app": "showcase", + "requires": [ + "platform-admin access to the identity Import Wizard (objectui app-shell identityImport.ts wraps the wizard onto POST /api/v1/auth/admin/import-users — the sys_user import cannot use the generic /data import because it must go through better-auth password hashing + credential creation)", + "a small scratch CSV of users (email required; the wizard parses it client-side into rows[])", + "for the `auto` invite-reachable path: an email/SMS transport so an invitation can be issued (dev `log` transport is sufficient to observe it); unreachable rows fall back to a one-time password" + ], + "knownGaps": [ + "observing the `auto` INVITE path needs a transport to capture the invitation; with the dev log transport it is observable, otherwise the invite-vs-fallback split is blocked(fixture). The one-time passwords (auto-fallback + all of temporary) are returned ONLY in the response — the result step must reveal them; they are never persisted" + ] + }, + "steps": [ + "as admin, open the Import Wizard for Users, upload the scratch CSV, map email (and name/phone) — the wizard drives POST /api/v1/auth/admin/import-users in ≤500-row batches with the chosen passwordPolicy", + "run with passwordPolicy `auto` (the default, framework#3236): deliverable rows get an invitation (set-your-password email/SMS), unreachable rows fall back to a one-time password revealed once in the result step; capture the per-row result (action + any temporaryPassword) and screenshot the reveal", + "run a second import with passwordPolicy `temporary`: EVERY row gets a per-row one-time password (no invitations); confirm each result row carries a temporaryPassword shown once", + "sign in as an imported user: for a `temporary` (or auto-fallback) row use the revealed one-time password; for an `auto` invite row, follow the invitation to set a password; confirm email+password sign-in then works", + "idempotency: re-run the SAME CSV in upsert mode (matchBy email) and confirm it upserts (updates) rather than duplicating — the endpoint matches on email/phone", + "confirm one-time passwords are NOT persisted anywhere: they appear only in the import response/result step, never in a later read of the user or any audit row", + "both-sides gate: as a NON-admin, POST /api/v1/auth/admin/import-users directly and capture the refusal (the endpoint is platform-admin-gated)" + ], + "acceptance": [ + { + "clause": "policy `auto` splits per row: deliverable rows get an invitation, unreachable rows fall back to a one-time password revealed once — the wizard result surfaces both outcomes", + "oracle": "api", + "verify": "the import response's per-row results show action + (for fallback rows) a temporaryPassword; deliverable rows show an invitation outcome (observed at the dev transport)", + "evidence": "the import response + the transport capture + the reveal screenshot" + }, + { + "clause": "policy `temporary` forces a per-row one-time password for EVERY row (no invitations)", + "oracle": "api", + "verify": "every result row under `temporary` carries a temporaryPassword; no invitation is issued", + "evidence": "the import response" + }, + { + "clause": "an imported user can sign in: the one-time password (temporary/auto-fallback) or the invitation-set password authenticates via email+password", + "oracle": "api", + "verify": "POST /api/v1/auth/sign-in/email for an imported user with the revealed/one-time credential returns a session", + "evidence": "the sign-in trace" + }, + { + "clause": "re-import is idempotent on upsert: re-running the same CSV (matchBy email) updates existing users rather than creating duplicates", + "oracle": "api", + "verify": "sys_user count for the imported emails is unchanged after the second run; the summary shows updated/skipped, not created", + "evidence": "the two import summaries + the sys_user read" + }, + { + "clause": "one-time passwords are never persisted: they exist only in the import response/result step, absent from any later user read or audit row", + "oracle": "api", + "verify": "a follow-up read of an imported user (and any audit row) contains no plaintext temporary password", + "evidence": "the follow-up reads" + }, + { + "clause": "the import is platform-admin-gated: a non-admin's direct POST /api/v1/auth/admin/import-users is refused server-side", + "oracle": "api", + "verify": "the forged non-admin request returns non-2xx and no users are created", + "evidence": "the refusal + the unchanged sys_user read" + } + ], + "negative": [ + "a `temporary` import that leaves any row WITHOUT a one-time credential (so the user can never sign in) is a FAIL", + "a one-time password persisted anywhere server-side (user row, audit log) is a security FAIL — it is response-only", + "a re-import that DUPLICATES users instead of upserting on the match key is a FAIL", + "a non-admin succeeding at import-users is a privilege FAIL — apply RUNNER rule 7" + ], + "variants": [ + "auto (default: invite reachable, one-time-password fallback for unreachable)", + "temporary (per-row one-time password for every row)", + "invite (force an invitation for every row; unreachable rows fail)", + "none (identity only — user sets a password later via OTP / magic link / reset)" + ], + "traps": ["wrong-persona", "seed-data-thin"], + "source": [ + "objectui packages/app-shell/src/views/identityImport.ts (IdentityPasswordPolicy 'auto'|'none'|'invite'|'temporary'; wraps ImportWizard onto POST /api/v1/auth/admin/import-users; ≤500-row batches; one-time passwords response-only, never persisted; upsert idempotent on email/phone)", + "packages/plugins/plugin-auth/src/admin-user-endpoints.ts (POST /api/v1/auth/admin/import-users — platform-admin-gated login-capable account creation; explicit-password/generatePassword resolution)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "new item: admin CSV identity import with password-policy matrix (auto/temporary/invite/none), imported-user sign-in, upsert idempotency, response-only one-time passwords, non-admin denied, grounded in objectui identityImport.ts + admin-user-endpoints.ts (PENDING-GAPS §G)", "ref": "claude/platform-test-checklist-ocwugl" } ] } ] diff --git a/docs/qa/platform-checklist/areas/integration-system.json b/docs/qa/platform-checklist/areas/integration-system.json index 72136ee4d7..44c6c1cd99 100644 --- a/docs/qa/platform-checklist/areas/integration-system.json +++ b/docs/qa/platform-checklist/areas/integration-system.json @@ -1,283 +1,1188 @@ { - "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md.", + "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md. FIXTURE MAP for this area (all in-repo, CI-deterministic): the showcase ships four declarative connectors (examples/app-showcase/src/system/connectors/index.ts — rest/openapi/mcp provider-bound instances + one enabled:false catalog descriptor), an outbound webhook (src/automation/webhooks — shipped inactive), a cron job (src/automation/jobs — showcase_health_sweep), an email template (src/system/emails — showcase_task_done_email) and two notify flows (src/automation/flows).", "area": "integration-system", - "title": "Integration & system services — connectors, webhooks, jobs, email templates", + "title": "Integration & system services — connectors, webhooks, jobs, email templates, notifications", "items": [ { "id": "integration-system.connector-declarative-boot", - "title": "A declarative provider-bound connector materializes at boot", + "title": "Provider-bound declarative connectors materialize at boot for every installed provider kind (rest/openapi/mcp), list with origin:'declarative' state:'ready', and dispatch end-to-end from connector_action", "since": "v15.1", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "api", - "fixtures": { "app": "showcase", "requires": ["the live provider:'mcp' demo connector instance shipped with the showcase (#2994/#3062)"] }, + "fixtures": { + "app": "showcase", + "requires": [ + "the three provider-bound showcase instances (examples/app-showcase/src/system/connectors/index.ts): showcase_status_api (provider:'rest', providerConfig.baseUrl self-pointing), showcase_status_openapi (provider:'openapi', file-path spec), showcase_mcp_tools (provider:'mcp', stdio fixture scripts/mcp-fixture.mjs) — #2994/#3062/#3056", + "the provider factories installed in objectstack.config.ts: ConnectorRestPlugin, ConnectorOpenApiPlugin, ConnectorMcpPlugin({ declarativeStdio: ['node'] })", + "the two dispatch flows: ShowcaseDeclarativeConnectorPingFlow (rest getHealth) and ShowcaseMcpConnectorEchoFlow (mcp echo_upper) — examples/app-showcase/src/automation/flows/index.ts" + ] + }, + "variants": [ + "provider: rest — providerConfig { baseUrl }, actions hand-derived by the factory", + "provider: openapi — providerConfig { spec, baseUrl? }; actions derived from the OpenAPI document's operations (getHealth)", + "provider: mcp — providerConfig { transport }; actions derived from the upstream's tools/list (echo_upper)", + "no provider — catalog descriptor (showcase_erp_catalog): registered as metadata only, NEVER in the runtime registry (#2612)" + ], "steps": [ - "boot the showcase; read the boot log for connector materialization", - "GET /api/v1/automation/connectors (route ledger: automation.listConnectors)", - "locate the declarative instance in the listing and read its descriptor" + "boot the showcase; capture the boot log lines 'Connector registered: (… origin: declarative)'", + "GET /api/v1/automation/connectors (route ledger: automation.listConnectors); the body is { connectors, total }", + "for each of the three instances, record its descriptor: origin, state, and the actions[] array (key/label/inputSchema/outputSchema/effect)", + "confirm showcase_erp_catalog is ABSENT from the runtime listing (it has no provider — descriptor only)", + "POST /api/v1/automation/showcase_declarative_connector_ping/trigger and .../showcase_mcp_connector_echo/trigger (automation.execute); read each run via GET /automation/:name/runs", + "author the DeclarativeConnectorEntrySchema negatives in a scratch package and build each: (a) providerConfig with NO provider, (b) provider-bound entry that also authors actions[], (c) provider-bound entry with inline authentication { type: 'api-key', … }, (d) provider: 'no_such_provider'; capture all four rejections", + "disable one declaration (enabled:false) and reboot; re-list" ], "acceptance": [ { - "clause": "the declarative instance appears in the connector listing with origin:'declarative' and a healthy state", + "clause": "all three provider variants appear in GET /automation/connectors with origin:'declarative' and state:'ready' — one clause verdict PER variant, none inferred from a sibling", + "oracle": "api", + "verify": "the listing contains showcase_status_api, showcase_status_openapi, showcase_mcp_tools each with origin 'declarative' + state 'ready' (engine.getConnectorDescriptors shape)", + "evidence": "the three descriptors, keyed by provider variant" + }, + { + "clause": "actions are DERIVED from each provider's upstream, not authored: the mcp instance's action set equals the fixture server's tools/list (echo_upper), the openapi instance's equals the document's operations (getHealth)", + "oracle": "api", + "verify": "descriptor actions[] against scripts/mcp-fixture.mjs and src/system/connectors/status-openapi.json", + "evidence": "the action arrays next to their upstream sources" + }, + { + "clause": "a materialized instance is DISPATCHABLE, indistinguishable from a hand-registered connector: both flows complete and their connector_action outputs land in flow variables (rest ping returns the health payload; mcp echo returns the uppercased string)", "oracle": "api", - "verify": "GET /automation/connectors contains the instance with origin:'declarative'", - "evidence": "the listing" + "verify": "the two triggered runs succeed; run output/variables carry the upstream results", + "evidence": "the two run records" }, { - "clause": "its declared actions are enumerable from the descriptor (the instance is usable, not just listed)", + "clause": "the authoring gate rejects each malformed entry with its located ADR-0097 message: providerConfig-without-provider ('`providerConfig` requires a `provider` …'), instance-authored actions ('must not author `actions` — the … provider derives them from the upstream at boot'), inline secrets ('must not inline secrets via `authentication`; reference credentials with `auth: { type, credentialRef }`')", + "oracle": "build", + "verify": "the three superRefine rejections match packages/spec/src/integration/connector.zod.ts DeclarativeConnectorEntrySchema verbatim on the quoted fragments", + "evidence": "the three error texts" + }, + { + "clause": "an unknown provider key is a HARD BOOT ERROR (fail loudly, ADR-0097 §Decision) — never a silently-dead connector", + "oracle": "log", + "verify": "boot with provider:'no_such_provider' aborts with an error naming the provider", + "evidence": "the fatal boot output" + }, + { + "clause": "materialization tracks the metadata: a disabled/removed declaration is gone from the listing after reboot (and torn down on reload) — it does not fossilize", "oracle": "api", - "verify": "the descriptor carries the action set the declaration authored", - "evidence": "the descriptor read" + "verify": "post-disable listing omits the instance", + "evidence": "the before/after listings" } ], - "negative": ["deleting/disabling the declaration and rebooting must remove/disable the instance — materialization tracks the metadata, it does not fossilize"], - "traps": ["stale-dist"], - "source": ["docs/plans/release-15.1-test-plan.md §B1 (#2994/#3062)", "packages/runtime/src/route-ledger.ts (GET /automation/connectors)", "packages/spec/src/integration/connector.zod.ts"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the 15.1 plan §B — connectors had no checklist coverage", "ref": "claude/platform-test-checklist-ocwugl" }] + "negative": [ + "the catalog descriptor (showcase_erp_catalog) appearing in the RUNTIME listing is a FAIL — descriptor vs instance is the #2612 boundary this whole surface is built on", + "any of the four malformed-entry builds passing clean is a FAIL (silent acceptance of an unmaterializable declaration)" + ], + "traps": [ + "stale-dist" + ], + "automated": { + "kind": "e2e", + "ref": "packages/qa/dogfood/test/showcase-declarative-mcp.dogfood.test.ts" + }, + "source": [ + "docs/plans/release-15.1-test-plan.md §B1 (#2994/#3062)", + "packages/spec/src/integration/connector.zod.ts (provider/providerConfig/auth keys + DeclarativeConnectorEntrySchema cross-field rejections, exact messages)", + "packages/spec/src/integration/connector-provider.ts (factory contract; adopt-declared-name; throw ⇒ hard boot error)", + "packages/services/service-automation/src/engine.ts (registerConnector origin 'declarative'; getConnectorDescriptors shape)", + "packages/runtime/src/route-ledger.ts (GET /automation/connectors, POST /automation/:name/trigger, GET /automation/:name/runs)", + "examples/app-showcase/src/system/connectors/index.ts + src/automation/flows/index.ts (the fixtures)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from the 15.1 plan §B — connectors had no checklist coverage", + "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] }, { "id": "integration-system.connector-degraded-recovery", - "title": "Unreachable connector upstream degrades gracefully and recovers atomically", + "title": "An unreachable connector upstream degrades that ONE instance (CONNECTOR_UPSTREAM_UNAVAILABLE) instead of failing boot, retries on 5s→300s backoff, and recovers atomically — while config faults stay boot-fatal", "since": "v15.1", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "api", + "fixtures": { + "app": "showcase", + "requires": [ + "the showcase_mcp_tools stdio instance — break its upstream deterministically by pointing providerConfig.transport.args at a nonexistent script (the exact experiment its own fixture comment prescribes, examples/app-showcase/src/system/connectors/index.ts)", + "a config-fault twin for the both-sides check: the same entry with an invalid providerConfig shape (e.g. transport.kind: 'carrier_pigeon')" + ] + }, "steps": [ - "point a declarative connector at an upstream, boot, confirm healthy", - "kill the upstream; restart the app server", - "read boot outcome + the instance descriptor; wait through a backoff cycle", - "restore the upstream; confirm recovery without another restart" + "boot with the broken upstream; time the boot and capture the '[Automation] connector … registered DEGRADED' warn with its degradedReason", + "GET /api/v1/automation/connectors; record the husk descriptor (state, degradedReason, actions)", + "POST the flow that dispatches it (showcase_mcp_connector_echo trigger); capture the dispatch failure", + "watch the log through at least three retry cycles; record the intervals", + "restore the upstream (fix the script path is a config CHANGE — instead restore by making the original path valid again, e.g. re-adding the file) WITHOUT restarting; wait one backoff cycle; re-read the descriptor", + "separately, boot the config-fault twin and capture that boot's outcome", + "trigger a metadata:reloaded reconcile (touch/save the entry) and confirm it retries immediately and resets the backoff" ], "acceptance": [ { - "clause": "boot does NOT hang or crash on the dead upstream — the instance lands as a degraded husk (descriptor state:'degraded')", + "clause": "boot COMPLETES on the dead upstream — the instance lands as a degraded husk: descriptor state:'degraded' with a degradedReason quoting the operational failure; no actions, and it still appears in GET /automation/connectors (visible, not vanished)", "oracle": "api", - "verify": "boot completes; GET /automation/connectors shows state:'degraded' for the instance", + "verify": "boot exit + the husk descriptor (engine.registerDegradedConnector: state 'degraded', empty handlers, stored degradedReason)", "evidence": "boot log + descriptor" }, { - "clause": "retries back off (no hot loop) and recovery swaps the live instance in atomically", + "clause": "the degrade path is taken ONLY for errors carrying code CONNECTOR_UPSTREAM_UNAVAILABLE (structural check, not instanceof); a configuration fault (invalid providerConfig) remains FATAL at boot — both sides of the #3017 classification", + "oracle": "log", + "verify": "the broken-upstream boot degrades; the config-fault twin's boot aborts with the factory's validation error (connector-provider-errors.ts contract)", + "evidence": "the two boot outcomes side by side" + }, + { + "clause": "dispatching a degraded instance fails FAST with a pointed connector-unavailable error that quotes the stored degradedReason — not a timeout hang, and distinguishable from 'no such connector/action'", + "oracle": "api", + "verify": "the connector_action step errors promptly; its message carries the degradedReason (engine.getConnectorDegradedReason feeds the refusal)", + "evidence": "the failed run record" + }, + { + "clause": "retries back off from DECLARATIVE_RETRY_BASE_MS (5s) doubling to the 300s ceiling — no hot loop against the dead upstream", "oracle": "log", - "verify": "retry lines show increasing intervals; after upstream restore the descriptor flips healthy without a server restart", - "evidence": "log excerpt + post-recovery descriptor" + "verify": "observed retry intervals ≈ 5s/10s/20s… (service-automation/src/plugin.ts constants); a config edit or reload reconcile resets the backoff and retries immediately", + "evidence": "timestamped log excerpt" + }, + { + "clause": "recovery is ATOMIC and restart-free: once the upstream is reachable a retry swaps the live instance in — descriptor flips to state:'ready' with the derived actions, and the previously-failing flow dispatch now succeeds", + "oracle": "api", + "verify": "post-recovery descriptor + a successful echo run, all without a server restart", + "evidence": "the recovered descriptor + run record" } ], - "negative": ["while degraded, invoking one of its actions must fail with a clear connector-unavailable error — not a timeout hang"], - "source": ["docs/plans/release-15.1-test-plan.md §B3 (#3049)"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the 15.1 plan §B3", "ref": "claude/platform-test-checklist-ocwugl" }] + "negative": [ + "a dead upstream taking the WHOLE boot down is the outage-amplification #3017 exists to prevent — FAIL", + "a degraded instance silently disappearing from GET /automation/connectors is a FAIL (operators must be able to SEE the husk and its reason)" + ], + "traps": [ + "stale-dist" + ], + "source": [ + "docs/plans/release-15.1-test-plan.md §B3 (#3049)", + "packages/spec/src/integration/connector-provider-errors.ts (CONNECTOR_UPSTREAM_UNAVAILABLE marker; structural isConnectorUpstreamUnavailable)", + "packages/services/service-automation/src/plugin.ts (DECLARATIVE_RETRY_BASE_MS=5000 / DECLARATIVE_RETRY_MAX_MS=300000; degradedInstances backoff + reload reconcile; boot fatal vs reload skip)", + "packages/services/service-automation/src/engine.ts (registerDegradedConnector; getConnectorDegradedReason feeding the dispatch refusal)", + "packages/services/service-automation/src/connector-degrade-cause.test.ts + degraded-register-cause.test.ts (unit pins)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from the 15.1 plan §B3", + "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] }, { "id": "integration-system.connector-stdio-default-deny", - "title": "Declarative stdio connectors are denied unless the host allowlists them", + "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": 1, + "revision": 2, "priority": "P1", "surface": "cli", + "fixtures": { + "app": "showcase", + "requires": [ + "the showcase_mcp_tools stdio instance (command 'node') + the host opt-in `new ConnectorMcpPlugin({ declarativeStdio: ['node'] })` in examples/app-showcase/objectstack.config.ts — removing that option IS the deny fixture (the connector file's own comment says 'try it')" + ] + }, + "variants": [ + "policy absent/false → deny every declarative stdio transport (default)", + "policy ['node'] with command 'node' → allowed (strict string equality)", + "policy ['node'] with a non-matching command (e.g. 'python3') → denied, allowlist quoted", + "http transport → never subject to the stdio policy (control)" + ], "steps": [ - "author a declarative stdio connector WITHOUT host allowlist opt-in; boot", - "add the allowlist opt-in; boot again" + "boot the stock showcase (allowlisted): confirm showcase_mcp_tools materializes state:'ready'", + "remove the declarativeStdio option from ConnectorMcpPlugin in objectstack.config.ts; boot; capture the failure verbatim", + "restore the option but change the connector's command to one NOT in the allowlist (e.g. 'python3' with the same script); boot; capture that failure", + "restore fully; author a scratch mcp instance with an HTTP transport pointing at any URL and confirm the stdio policy does not touch it (it may degrade on unreachability — that is the OTHER item)", + "GET /api/v1/automation/connectors after the allowlisted boot" ], "acceptance": [ { - "clause": "the non-opted-in stdio instance is refused (boot fatal or reload skip, per the documented behavior) with a message naming the allowlist", + "clause": "the non-opted-in stdio instance refuses with the message naming the mechanism: 'declares a stdio transport (command \\'node\\'), but declarative stdio transports are disabled by default — a stdio transport launches a local process …' (connector-mcp provider, #3055)", + "oracle": "log", + "verify": "boot output contains the deny message from packages/connectors/connector-mcp/src/mcp-provider.ts; the fixture process is never spawned", + "evidence": "the refusal line" + }, + { + "clause": "an allowlist MISMATCH is refused quoting both the offending command and the configured allowlist: \"stdio transport with command 'python3', which is not in the host's declarativeStdio allowlist [node]\" — equality is strict, no prefix/glob creep", "oracle": "log", - "verify": "boot output for the deny case", + "verify": "the mismatch boot's error text carries command + allowlist", "evidence": "the refusal line" }, { - "clause": "the allowlisted boot materializes the instance normally", + "clause": "the deny is classified as a CONFIGURATION fault: boot-fatal at start (and a skipped entry on reload) — NOT a degraded husk retrying forever toward a process the host never authorized", + "oracle": "log", + "verify": "the denied boots abort; no 'registered DEGRADED' line and no retry loop for the denied instance", + "evidence": "boot output" + }, + { + "clause": "the allowlisted boot materializes the instance normally (state:'ready', echo_upper action derived) — the opt-in works and stays scoped to the exact command", "oracle": "api", - "verify": "GET /automation/connectors lists it healthy", - "evidence": "the listing" + "verify": "GET /automation/connectors shows showcase_mcp_tools ready on the stock config", + "evidence": "the descriptor" } ], - "negative": ["the deny must be default — absence of any allowlist config means refused, never silently spawned processes"], - "source": ["docs/plans/release-15.1-test-plan.md §B4 (#3059)"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the 15.1 plan §B4", "ref": "claude/platform-test-checklist-ocwugl" }] + "negative": [ + "absence of ANY allowlist config silently spawning a subprocess from metadata is the security failure #3055 closed — a spawned fixture process on the deny boot is a P0-severity FAIL regardless of this item's priority", + "the deny surfacing as state:'degraded' with backoff retries is a FAIL — an unauthorized command must not be retried into existence" + ], + "automated": { + "kind": "unit", + "ref": "packages/connectors/connector-mcp/src/mcp-provider.test.ts" + }, + "source": [ + "docs/plans/release-15.1-test-plan.md §B4 (#3059)", + "packages/connectors/connector-mcp/src/mcp-provider.ts (#3055 policy: default deny, strict-equality allowlist, both messages)", + "examples/app-showcase/objectstack.config.ts (declarativeStdio: ['node'] opt-in) + src/system/connectors/index.ts (the 'remove that option and boot fails loudly — try it' fixture note)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from the 15.1 plan §B4", + "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] }, { "id": "integration-system.connector-spec-path-no-escape", - "title": "Connector openapi spec paths resolve package-relative and reject escapes", + "title": "Connector openapi spec file refs resolve package-relative only: './…' works, absolute and '../' escapes reject with the confinement error, missing files fail loudly", "since": "v15.1", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", "surface": "build", + "fixtures": { + "app": "showcase", + "requires": [ + "the shipped file-path fixture: showcase_status_openapi with providerConfig.spec './src/system/connectors/status-openapi.json' (#3016) — the happy path is stock", + "scratch copies of that entry for the three rejection probes (absolute path, '../' escape, missing file)" + ] + }, + "variants": [ + "spec: inline OpenAPI document object (no file read)", + "spec: package-relative file path './…' (resolved against packageRoot, confined)", + "spec: http(s) URL (fetched; unreachability is the degrade item's territory)", + "rejection: absolute path (/etc/… or C:\\…)", + "rejection: '../'-escaping relative path", + "rejection: missing/unreadable file" + ], "steps": [ - "author providerConfig.spec: './openapi.json' inside the package; build/boot", - "author providerConfig.spec: '../outside.json'; build/boot" + "boot the stock showcase; confirm showcase_status_openapi materialized with the spec-derived getHealth action", + "author scratch entries and boot each: spec '/etc/hostname' (absolute), spec '../../outside.json' (escape), spec './does-not-exist.json' (missing); capture each error verbatim", + "author one with a nested traversal that RESOLVES inside the root after normalization (e.g. './src/../src/system/connectors/status-openapi.json') and record whether it is accepted — the guard rejects on the RESOLVED path, not on the substring", + "verify boot-vs-reload policy: repeat the missing-file probe via a metadata reload and confirm it is skipped-with-log instead of fatal" ], "acceptance": [ { - "clause": "the package-relative path resolves and the connector's actions derive from the spec", + "clause": "the package-relative path resolves and the connector's actions derive from the document (getHealth on the descriptor; dispatch covered by connector-declarative-boot)", "oracle": "api", - "verify": "descriptor carries the spec-derived actions", + "verify": "descriptor carries the spec-derived action set on the stock boot", "evidence": "descriptor read" }, { - "clause": "the '../' escape is rejected with a located error — path traversal out of the package is not a supported shape", + "clause": "an absolute path is rejected with the exact guard: \"package file ref '

' is absolute — file refs must be relative to the declaring stack/package root.\" — including Windows drive-letter forms", "oracle": "build", - "verify": "build/boot refuses the escaping path", + "verify": "boot error text matches createPackageFileLoader (packages/services/service-automation/src/plugin.ts)", "evidence": "the error" + }, + { + "clause": "a path escaping the root after RESOLUTION is rejected: \"package file ref '

' escapes the stack/package root — reads are confined to ''.\" — while an inside-resolving './a/../b' form passes (the check is on the resolved path)", + "oracle": "build", + "verify": "the '../' probe errors with the confinement text; the normalized-inside probe boots", + "evidence": "the two outcomes" + }, + { + "clause": "a missing/unreadable file fails LOUDLY with the resolved path in the message ('could not be read (resolved to …)') — boot-fatal at start, skipped-with-log on reload, per the ADR-0097 reconcile policy", + "oracle": "build", + "verify": "the missing-file boot aborts; the reload path logs + skips", + "evidence": "both captures" } ], - "source": ["docs/plans/release-15.1-test-plan.md §B5 (#3024)"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the 15.1 plan §B5", "ref": "claude/platform-test-checklist-ocwugl" }] + "negative": [ + "any read landing OUTSIDE the declaring package root is a security FAIL whatever the error text — the confinement, not the message, is the contract", + "a missing spec producing a silently-actionless connector (instead of the loud failure) is a FAIL" + ], + "source": [ + "docs/plans/release-15.1-test-plan.md §B5 (#3024)", + "packages/services/service-automation/src/plugin.ts (createPackageFileLoader — all three exact error strings, #3016)", + "packages/spec/src/integration/connector.zod.ts (providerConfig.spec three-form doc) + connector-provider.ts (loadPackageFile contract incl. host-without-fs behavior)", + "examples/app-showcase/src/system/connectors/index.ts (showcase_status_openapi — the shipped happy-path fixture)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from the 15.1 plan §B5", + "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] }, { "id": "integration-system.connector-descriptor-audit", - "title": "Descriptor-only connector contracts are audited at boot", + "title": "Descriptor-only connector contracts are audited at boot: declared-with-actions-but-unregistered warns with names + remedy; enabled:false is the deliberate, quiet catalog opt-out", "since": "v15.1", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", "surface": "cli", + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_erp_catalog — the shipped enabled:false catalog descriptor WITH actions (get_invoice, post_journal_entry): the stock boot is the quiet side of this gate", + "a scratch copy of it with enabled:true (and no provider) for the loud side" + ] + }, "steps": [ - "declare connector actions that have no runtime registration; boot and read the audit", - "set enabled:false on the declaration; boot again" + "boot the stock showcase and grep the full boot log for the inert-connector audit warning — there must be NONE (the only actions-bearing descriptor is enabled:false)", + "add the scratch enabled:true descriptor (actions, no provider, no plugin registration); boot; capture the warning verbatim", + "add a same-named RUNTIME registration for it (any connector plugin) and boot again — the audit must go quiet because the name is now live", + "flip the scratch entry back to enabled:false; boot; confirm quiet" ], "acceptance": [ { - "clause": "actions without runtime registration produce a boot warning naming the gap (declared ≠ delivered surfaces loudly, PD#10)", + "clause": "the loud side names names and prescribes the fix: '[Automation] N declarative connector(s) declare actions but are not registered in the connector registry — the connector_action node cannot dispatch them: . … Install/instantiate the matching connector plugin, or mark a deliberate catalog-only entry with `enabled: false` to silence this warning.' (PD#10: declared ≠ delivered surfaces loudly)", "oracle": "log", - "verify": "the warning line names the connector + missing actions", + "verify": "the warning matches auditDeclaredConnectors (packages/services/service-automation/src/plugin.ts) and lists the scratch connector's name", "evidence": "log excerpt" }, { - "clause": "enabled:false silences the audit for that declaration — the opt-out is deliberate and quiet", + "clause": "all three quiet conditions are individually verified: (a) enabled:false descriptor — quiet; (b) descriptor with a same-name runtime registration — quiet; (c) provider-bound instances — never audited by this gate (they materialize instead)", "oracle": "log", - "verify": "no warning on the disabled boot", - "evidence": "log excerpt" + "verify": "no audit warning on the stock boot, on the registered boot, or for the three provider-bound showcase instances", + "evidence": "the grepped boot logs per condition" + }, + { + "clause": "the audit re-runs on metadata reload, not only at boot — enabling the scratch descriptor at runtime surfaces the warning without a restart", + "oracle": "log", + "verify": "the warn appears after the reload reconcile (plugin.ts wires auditDeclaredConnectors on both paths)", + "evidence": "timestamped log" } ], - "source": ["docs/plans/release-15.1-test-plan.md §B6 (#2985)"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the 15.1 plan §B6", "ref": "claude/platform-test-checklist-ocwugl" }] + "negative": [ + "a stock showcase boot emitting the inert-connector warning is a FAIL — either the shipped catalog descriptor lost its enabled:false or the audit's silencing contract broke", + "the warning firing for a provider-bound instance is a FAIL (it is an instance declaration, not a descriptor — #2977/ADR-0097 carve-out in findInertDeclaredConnectors)" + ], + "automated": { + "kind": "unit", + "ref": "packages/services/service-automation/src/connector-descriptor-audit.test.ts" + }, + "source": [ + "docs/plans/release-15.1-test-plan.md §B6 (#2985)", + "packages/services/service-automation/src/plugin.ts (auditDeclaredConnectors — exact warning text; boot + reload wiring)", + "packages/spec/src/integration/connector.zod.ts (enabled:false = deliberate catalog descriptor, #2612)", + "examples/app-showcase/src/system/connectors/index.ts (showcase_erp_catalog — the shipped quiet fixture)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from the 15.1 plan §B6", + "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] }, { "id": "integration-system.flow-connector-picker", - "title": "The flow designer's connector picker lists instances and marks declarative ones", + "title": "The flow designer's connector picker mirrors GET /automation/connectors: same instances, declarative ones annotated, actions and their input schemas offered per pick", "since": "v15.1", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", "surface": "browser", - "personas": ["admin"], + "personas": [ + "admin" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the stock showcase connector registry: three declarative instances + the plugin-registered rest/slack connectors from objectstack.config.ts (so the picker has BOTH origins to distinguish)" + ] + }, "steps": [ - "in the flow designer, add a connector_action node", - "open the connector picker; screenshot; compare with GET /automation/connectors" + "before the browser: GET /api/v1/automation/connectors and record { connectors, total } — this is the ground truth the picker must mirror", + "in Studio, open a flow (e.g. showcase_declarative_connector_ping) and add/select a connector_action node", + "open the connector picker; screenshot; only AFTER the screenshot, read the DOM list", + "pick showcase_mcp_tools; record which actions are offered and screenshot the action + input form for echo_upper", + "pick a plugin-registered connector and compare its presentation with the declarative one's annotation" ], "acceptance": [ { - "clause": "the picker lists the same instances the API reports, with declarative instances annotated", + "clause": "the picker lists exactly the instances the API reports — no extras (a picker inventing catalog descriptors would let authors wire undispatchable nodes), none missing", "oracle": "screenshot", - "verify": "picker screenshot cross-checked against the connector listing", - "evidence": "screenshot + API read" + "verify": "picker screenshot cross-checked name-by-name against the pre-captured listing", + "evidence": "screenshot + API read, diffed" + }, + { + "clause": "declarative instances carry their annotation (origin distinguishable from plugin-registered), per the descriptor's origin field the API serves", + "oracle": "screenshot", + "verify": "the three declarative entries are visually marked; the plugin rest/slack ones are not", + "evidence": "annotated screenshot" + }, + { + "clause": "picking an instance offers its DERIVED actions with their input schemas (echo_upper for the mcp instance) — the ADR-0022 descriptor pipeline reaches the designer end-to-end", + "oracle": "dom", + "verify": "action list + input fields match the descriptor's actions[].inputSchema, read only after the screenshot confirmed render", + "evidence": "screenshot + DOM excerpt" } ], - "traps": ["stale-console-bundle", "hydration-race"], - "source": ["docs/plans/release-15.1-test-plan.md §B2 (objectui#2563)"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial import from the 15.1 plan §B2", "ref": "claude/platform-test-checklist-ocwugl" }] + "negative": [ + "a picker entry for a connector the API does not list (or vice versa) is a FAIL — the network response is the authority, not the panel" + ], + "traps": [ + "stale-console-bundle", + "hydration-race", + "wrong-panel" + ], + "source": [ + "docs/plans/release-15.1-test-plan.md §B2 (objectui#2563)", + "packages/services/service-automation/src/engine.ts (getConnectorDescriptors — the designer-facing shape incl. origin + actions[].inputSchema, ADR-0022)", + "packages/runtime/src/route-ledger.ts (GET /automation/connectors)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from the 15.1 plan §B2", + "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] }, { "id": "integration-system.webhook-lifecycle", - "title": "Webhooks: live trigger events register and fire; retired trigger kinds are rejected", + "title": "Outbound webhooks materialize (spec object→object_name, isActive→active), fire per trigger variant through the sys_http_delivery outbox with HMAC + timeout honored, reject retired trigger kinds, and never clobber admin edits", "since": "v15", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "mixed", - "fixtures": { "app": "any", "requires": ["a reachable webhook receiver (local echo server on the run's own port range)"] }, + "fixtures": { + "app": "showcase", + "requires": [ + "a reachable webhook receiver (local echo server on the run's own port range) — point the shipped showcase_task_changed row at it, or author a scratch webhook", + "the shipped fixture: showcase_task_changed (object showcase_task, triggers create/update/delete, isActive:false ON PURPOSE — flipping it active in Setup is part of the test, examples/app-showcase/src/automation/webhooks/index.ts)", + "a predicate multi-write path for the bulk variants (update/delete with multi:true on showcase_task)" + ] + }, + "variants": [ + "trigger: create (data.record.created → per-record payload with recordId)", + "trigger: update (data.record.updated)", + "trigger: delete (data.record.deleted)", + "trigger: bulk_update (aggregate data.records.updated → { object, matched }, NO recordId/body — #4639)", + "trigger: bulk_delete (aggregate data.records.deleted, same shape)", + "retired: undelete — no event source exists (#3196); parse-rejected", + "retired: api (manual fire) — no fire path exists (#3196); parse-rejected" + ], "steps": [ - "author a webhook on a live record event (per the trigger set packages/spec/liveness/webhook.json documents as live); boot", - "mutate a matching record; capture the delivery at the receiver", - "author a webhook using a RETIRED trigger kind (the enforce-or-remove sweep removed undelete/api); build" + "boot the showcase; verify the materializer bridge: read sys_webhook over /api/v1/data and locate showcase_task_changed with object_name:'showcase_task', active:false, managed_by:'package', and the envelope in definition_json", + "in Setup → Integrations → Webhooks flip the row active and point url at the local receiver (an admin edit — it must stamp customized)", + "create, update, then delete a showcase_task; capture the three deliveries at the receiver (headers incl. the HMAC signature when a secret is set, body incl. recordId)", + "run a predicate multi-update and multi-delete (multi:true) matching several rows; capture the bulk deliveries and their { object, matched } shape", + "read sys_http_delivery over /api/v1/data: one row per delivery with status/attempts/lastStatusCode", + "kill the receiver and mutate again; re-read the delivery row through its retry/failure states", + "author a scratch webhook with triggers:['undelete'] and one with ['api']; build both and capture the parse errors", + "redeploy/reboot and confirm the admin-edited row survived re-seed (customized rows are never clobbered)" ], "acceptance": [ { - "clause": "the live-event webhook delivers to the receiver with the event payload on the matching mutation", + "clause": "authoring is LIVE, not a no-op: the declared webhook materializes into the sys_webhook row the dispatcher reads, with the two documented remaps (object→object_name, isActive→active) and the envelope in definition_json (#3461/#3489)", + "oracle": "api", + "verify": "the sys_webhook row fields against bootstrap-declared-webhooks mapWebhookToRow", + "evidence": "the row read" + }, + { + "clause": "each per-record trigger variant delivers exactly its event: create/update/delete each produce one receiver hit whose payload names the event and carries the recordId — verified per-variant, none inferred", + "oracle": "network", + "verify": "the three captured requests, keyed by variant", + "evidence": "receiver logs" + }, + { + "clause": "the bulk pair delivers the AGGREGATE shape — { object, matched } with no recordId and no record body — and only to webhooks that opted into bulk_update/bulk_delete; per-record subscribers do NOT receive a fabricated per-record event for a predicate write (#4639/#4626)", "oracle": "network", - "verify": "receiver logs the POST with the record event", - "evidence": "the received request" + "verify": "bulk delivery bodies + absence of per-record deliveries for the same predicate write on a create/update/delete-only subscription", + "evidence": "receiver logs for both subscriptions" + }, + { + "clause": "delivery mechanics honor the authored envelope: custom headers attached, HMAC signature present when secret is set, timeoutMs applied — and every attempt is durably observable as a sys_http_delivery row (status pending/in_flight/success/failed/dead, attempts, lastStatusCode)", + "oracle": "api", + "verify": "receiver-side headers + the outbox rows over the data API", + "evidence": "headers + delivery rows" }, { - "clause": "a retired trigger kind fails at parse/build with located guidance — never registers silently dead", + "clause": "a retired trigger kind fails at parse with the enum rejection — WebhookTriggerType is exactly [create, update, delete, bulk_update, bulk_delete]; undelete/api never register silently dead (the #3196 enforce-or-remove gate the #3358 sweep verified)", "oracle": "build", - "verify": "authoring the retired kind errors (same gate the #3358 enforce-or-remove sweep verified)", - "evidence": "the build error" + "verify": "both scratch builds error on the trigger value", + "evidence": "the two build errors" + }, + { + "clause": "seed-not-clobber: the admin-edited row (customized:true) survives redeploys — the deactivation/receiver-URL edit is still there after reboot", + "oracle": "api", + "verify": "post-reboot sys_webhook row keeps the admin's values (bootstrap-declared-webhooks.ts:132-145)", + "evidence": "before/after row reads" } ], - "negative": ["an unreachable receiver must surface delivery failure observably (log/status), not drop silently"], - "source": ["packages/spec/liveness/webhook.json", "#3358 §9 (webhook undelete/api trigger removal gate)"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — webhook kind had no checklist coverage", "ref": "claude/platform-test-checklist-ocwugl" }] + "negative": [ + "an unreachable receiver must surface as failed/dead sys_http_delivery rows (and retry per the outbox schedule) — a dropped delivery with no durable trace is a FAIL", + "a stored row whose triggers contain an unknown value must be dropped LOUDLY by the dispatcher (the #3196 drift-guard warn: 'dead while looking armed in Setup') — silent armed-looking deadness is a FAIL", + "connector-attached `webhooks`/`triggers` (integration/connector.zod.ts) are NOT dispatched by anything (#3197, said in-schema) — a run must not tick them as live, and a delivery appearing from one would be a spec-contract FAIL" + ], + "traps": [ + "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 enum + why undelete/api are absent; materialization contract; strict shape #4001)", + "packages/spec/liveness/webhook.json (all 11 props live via the #3489 bridge; per-prop line refs)", + "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts + auto-enqueuer.ts (remaps; trigger→event mapping incl. the #4639 bulk pair; #3196 unknown-trigger warn; seed-not-clobber)", + "packages/services/service-messaging/src/http-outbox.ts (delivery statuses, attempts, redeliver contract) + plugin-webhooks/webhook-outbox-plugin.ts (sys_http_delivery nav)", + "examples/app-showcase/src/automation/webhooks/index.ts (the shipped inactive fixture and its activation story)", + "#3358 §9 (webhook undelete/api trigger removal gate)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — webhook kind had no checklist coverage", + "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] }, { "id": "integration-system.job-scheduled-run", - "title": "A job metadata item schedules, executes, and records its run", + "title": "Job metadata schedules through every ScheduleSchema variant, executes with retry/timeout enforced, records sys_job/sys_job_run truth, and refuses the closed doors (id key, runtime create, missing handler)", "since": "v15", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "mixed", - "fixtures": { "app": "any", "requires": ["a job authored with a near-term schedule (or a manually-runnable job) — packages/spec/src/system/job.zod.ts is the shape"] }, + "fixtures": { + "app": "showcase", + "requires": [ + "the shipped cron fixture: showcase_health_sweep (schedule cron '0 1 * * *' UTC, handler 'sweepProjectHealth' registered in defineStack({ functions }), retryPolicy { maxRetries: 2, backoffMs: 5000, backoffMultiplier: 2 }, timeout 300000 — examples/app-showcase/src/automation/jobs/index.ts; the handler-missing regression here was #4774/#4888)", + "scratch jobs in a writable package for the interval / once / failing / timing-out probes (near-term schedules so the run observes real executions)" + ] + }, + "variants": [ + "schedule { type: 'cron', expression, timezone (default UTC) } — cron-job-adapter.ts:76-77", + "schedule { type: 'interval', intervalMs } — cron-job-adapter.ts:82", + "schedule { type: 'once', at: ISO datetime } — cron-job-adapter.ts:87", + "execution status: success | failed | timeout (JobExecutionStatus; 'running' is the in-flight state)", + "rejection: job.id (retired 17.0.0, #4667 — guidance-carrying parse error)", + "rejection: unknown key (strictObject #4001; aliases cron/interval→schedule, fn→handler)" + ], "steps": [ - "author a job per the spec shape in a writable package; boot", - "confirm registration (meta read / boot log), then let it fire or trigger it manually", - "read the run evidence (log lines / run records) and the job's side effect" + "boot the showcase; capture the AppPlugin job registration path for showcase_health_sweep (bundle jobs → IJobService.schedule on kernel:ready)", + "read sys_job over /api/v1/data: the row is keyed by NAME (the adapter mints its own row id) with the schedule persisted", + "author three scratch jobs (interval ~5s, once at now+1min, and a cron) with observable side effects; boot; let each fire; read sys_job_run rows and sys_job.last_run_at/last_status/run_count", + "author a deliberately-throwing job with retryPolicy { maxRetries: 2, backoffMs: 1000, backoffMultiplier: 2 }; let it exhaust; capture run rows + failure_count", + "author a job whose handler sleeps past a small `timeout`; capture the run's status", + "author a job with enabled:false and one whose handler string names NO registered function; boot; capture how each is skipped", + "author a job carrying `id: 'x'` and one with a stray key; build both; capture the errors", + "attempt to create a job at runtime through the meta door and capture the refusal (allowRuntimeCreate:false, #4509)" ], "acceptance": [ { - "clause": "the job registers at boot and its execution is observable (run record or timestamped log) with the side effect present", + "clause": "every ScheduleSchema variant actually schedules and fires — cron (with timezone), interval (intervalMs), once (at) — each verified by its OWN sys_job_run row and side effect, not by registration lines alone", + "oracle": "api", + "verify": "per-variant run rows + the side effect over the data API (adapters honor all three shapes: cron-job-adapter.ts:71-88; db adapter persists them: db-job-adapter.ts:233-245)", + "evidence": "run rows + side-effect reads, keyed by variant" + }, + { + "clause": "execution truth is durable and name-keyed: sys_job upserts by name; every execution writes a sys_job_run row and bumps last_run_at/last_status/run_count/failure_count", + "oracle": "api", + "verify": "the counters move with each observed run", + "evidence": "before/after row reads" + }, + { + "clause": "a failing job retries per the CONVERGED policy — delay = backoffMs × multiplier^(retry-1) up to maxRetries — then records status 'failed' with the error message; a policy-less job gets ONE attempt (maxRetries defaults to 0 since 17.0.0, #4661 — a run asserting 3 default retries is testing the old world)", "oracle": "log", - "verify": "boot registration line + execution line + the side effect verified over the data API", - "evidence": "log excerpts + API read" + "verify": "attempt count + spacing in the run rows/log for the throwing probe; the no-policy probe shows exactly one attempt", + "evidence": "run rows + timestamped log" }, { - "clause": "a failing job surfaces an ERROR with the failure cause — not a silent skip", + "clause": "an over-`timeout` run is recorded with execution status 'timeout' (the in-flight handler is abandoned, not force-cancelled — as documented), and timeouts COUNT as failures for the retry loop (#3494)", + "oracle": "api", + "verify": "the timeout probe's run row status", + "evidence": "the run row" + }, + { + "clause": "the two skip doors are LOUD and distinct: enabled:false skips scheduling at registration; a handler naming no registered function skips with '[AppPlugin] job handler not found in bundle.functions — skipping' — the exact silent-no-op that let the showcase sweep never run for months (#4774/#4888)", "oracle": "log", - "verify": "a deliberately-throwing job body produces the ERROR line", - "evidence": "log excerpt" + "verify": "both skip lines present; neither job has sys_job_run rows", + "evidence": "log excerpts + absence of runs" + }, + { + "clause": "the closed doors refuse loudly: authoring `id` errors with the #4667 prescription ('`job.id` was removed … `name` IS the job's identity everywhere … os migrate meta --from 16'); a stray key errors naming the key (aliases steer cron/interval→schedule, fn→handler); runtime create is refused (allowRuntimeCreate:false — handler strings cannot resolve outside the compiled bundle, #4509)", + "oracle": "build", + "verify": "the parse errors + the runtime-create refusal", + "evidence": "the error texts" } ], - "source": ["packages/spec/src/system/job.zod.ts", "packages/spec/liveness/job.json"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — job kind had no checklist coverage", "ref": "claude/platform-test-checklist-ocwugl" }] + "negative": [ + "a declared job that never runs WITHOUT one of the two loud skip lines is a FAIL against the scheduler, not thin data — 'nothing reports this' was the actual #4774 bug", + "a runtime-created job row saving cleanly is a FAIL (the #4509 door is closed precisely because such a job could never run)" + ], + "traps": [ + "stale-dist", + "seed-data-thin" + ], + "source": [ + "packages/spec/src/system/job.zod.ts (ScheduleSchema discriminated union; JOB_ID_RETIRED; retryPolicy/timeout docs incl. the 17.0.0 maxRetries default flip #4661; JobExecutionStatus)", + "packages/spec/liveness/job.json (per-prop verdicts + the #4509 closed-door rationale)", + "packages/runtime/src/app-plugin.ts:790-855 (registration, enabled/handler skip lines)", + "packages/services/service-job/src/cron-job-adapter.ts + db-job-adapter.ts (all three schedule shapes; sys_job/sys_job_run persistence) + run-with-policy.ts (retry/timeout enforcement, #3494)", + "examples/app-showcase/src/automation/jobs/index.ts (showcase_health_sweep fixture + its #4774/#4888 history)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — job kind had no checklist coverage", + "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] }, { "id": "integration-system.email-template-render", - "title": "Email templates resolve variables and dispatch through the configured channel", + "title": "Email templates materialize to sys_email_template, resolve (name, locale) with en-US fallback, render {{path}} holes, gate on required variables and active:false, survive admin edits across redeploys — and the raw POST /api/v1/email/send door authenticates, refuses anonymous, and 400s malformed input", "since": "v15", "status": "active", - "revision": 1, + "revision": 3, "priority": "P2", "surface": "mixed", "fixtures": { - "app": "any", - "requires": ["an email_template authored per packages/spec/src/system/email-template.zod.ts + a capturable email channel (dev transport / log capture — no real SMTP needed)"], - "knownGaps": ["no stock example exercises an email template end-to-end; needs a small fixture flow with a notify/email step"] + "app": "showcase", + "requires": [ + "the shipped fixture: showcase_task_done_email (category workflow, subject '✅ Task done: {{title}}', variables title required / project optional — examples/app-showcase/src/system/emails/index.ts)", + "a capturable email channel (dev transport / log capture — no real SMTP needed)", + "a second-locale scratch copy (same name, locale zh-CN) for the i18n-bundle clause" + ], + "knownGaps": [ + "no stock example exercises an email template end-to-end; needs a small fixture flow with a notify/email step", + "refinement of the gap above (2026-08-07 survey): the TEMPLATE fixture now exists (showcase_task_done_email) and the send path is pinned by packages/qa/dogfood/test/email-template-materialization.dogfood.test.ts, but no shipped FLOW invokes sendTemplate — the flow-driven half still needs the fixture flow, or the send must be driven directly via IEmailService.sendTemplate in the run" + ] }, + "variants": [ + "locale resolution: exact (name, locale) match / en-US fallback / two-locale bundle picking by recipient locale", + "body: bodyHtml + bodyText authored / bodyText omitted (service derives text from HTML — omitted from the ROW, not nulled)", + "gate: active:false → TEMPLATE_INACTIVE", + "gate: missing required variable → fail-fast (requireVars)", + "rejection: unknown key (strictObject; aliases title→subject, content/html→bodyHtml, text→bodyText, from/sender→fromOverride — #5013)", + "rejection: name not dotted snake_case" + ], "steps": [ - "author an email_template with variables; reference it from a flow notify/email step", - "run the flow with known variable values", - "capture the rendered output at the dev transport / log" + "boot the showcase; read sys_email_template over /api/v1/data and locate showcase_task_done_email: bodyHtml→body_html remap, variables in variables_json, managed_by:'package'", + "drive a send with full data ({ title, project }) through IEmailService.sendTemplate (or the fixture flow once it exists); capture the rendered subject/body at the dev transport", + "drive a send MISSING the required `title`; capture the fast failure", + "set the row inactive (or author active:false) and send; capture the TEMPLATE_INACTIVE error", + "author the zh-CN twin; send to a zh-CN-locale recipient and to an unmatched-locale recipient; capture which row rendered each", + "author a template with fromOverride + replyTo and verify both on the outbound message", + "edit the template wording as an admin in Studio (stamps customized), redeploy/reboot, and re-read the row", + "edit the DECLARED source template and metadata-reload WITHOUT a reboot — the single item re-materializes (email_template is allowRuntimeCreate:true; the plugin subscribes to metadata changes)", + "build the two rejection probes (stray key `body`; name 'BadName') and capture the errors", + "drive the raw send door POST /api/v1/email/send three ways: (a) authed with a well-formed message { to, subject, bodyHtml } → capture status + the dev-transport landing; (b) anonymous (no session) → capture status; (c) a non-object / malformed body → capture the envelope" ], "acceptance": [ { - "clause": "the rendered subject/body substitute every variable with the run's values — no {{placeholder}} residue", + "clause": "the authored template is what actually renders: subject/body substitute every {{path}} hole with the run's values, and the AUTHORED wording (not a built-in or stale copy) reaches the transport — the exact three-break disconnect #4509 closed (engine registration + managed_by stamping + bridge write)", "oracle": "log", - "verify": "captured output contains the substituted values and zero unresolved placeholders", + "verify": "captured render contains the authored strings with substituted values and zero unresolved {{placeholder}} residue", "evidence": "the captured render" }, { - "clause": "a template referencing an undefined variable surfaces a located authoring/run error, not a silently-empty substitution", + "clause": "a send missing a REQUIRED variable fails fast with a located error naming the variable (requireVars, email-service.ts) — never a silently-empty substitution; the optional variable's absence does not fail the send", + "oracle": "log", + "verify": "the missing-title send errors; a missing-project send renders", + "evidence": "the two outcomes" + }, + { + "clause": "active:false is a real gate: sendTemplate returns TEMPLATE_INACTIVE (also the withdrawal mechanism — deleting a declared template deactivates rows rather than destroying them)", "oracle": "log", - "verify": "the undefined-variable case errors visibly", + "verify": "the inactive send's error code", "evidence": "the error" + }, + { + "clause": "(name, locale) resolution picks the best locale row and falls back to en-US — two rows with one name are an i18n bundle, both reachable by recipient locale", + "oracle": "log", + "verify": "the zh-CN recipient gets the zh-CN render; the unmatched recipient gets the fallback", + "evidence": "the two renders" + }, + { + "clause": "fromOverride and replyTo are honored on the outbound message; an omitted bodyText is derived from HTML at send time (and stays absent on the row so re-seeds never blank it)", + "oracle": "log", + "verify": "outbound headers + the derived text alternative", + "evidence": "the captured message" + }, + { + "clause": "both provenance protections hold: an admin-edited (customized) row survives redeploys, AND a declared-source edit re-materializes the single item on metadata reload without a restart", + "oracle": "api", + "verify": "post-reboot row keeps the admin wording; the reload path updates the untouched declared row", + "evidence": "before/after row reads for both paths" + }, + { + "clause": "authoring rejections are loud and prescriptive: a stray key errors naming it with the alias prescription (content→bodyHtml per #5013 — landing on the REQUIRED body so the rename renders); a non-dotted-snake-case name errors at parse", + "oracle": "build", + "verify": "the two probe errors against EmailTemplateDefinitionSchema", + "evidence": "the error texts" + }, + { + "clause": "the raw transactional send door (POST /api/v1/email/send → IEmailService.send, complementary to the sendTemplate path above) authenticates and validates: an AUTHED well-formed message lands at the dev transport (200 with result.status 'sent'); an ANONYMOUS send is refused 401 UNAUTHENTICATED (the #3963 unconditional gate — the api.requireAuth opt-out is retired); a MALFORMED body is refused 400 with a ledgered envelope code (INVALID_REQUEST for a non-object body, VALIDATION_FAILED for a bad message shape) — never a 500 for caller-fixable input, and a runtime with no email provider answers 501 NOT_IMPLEMENTED rather than a fake success", + "oracle": "api", + "verify": "the three POST /api/v1/email/send responses: authed 200 + dev-transport capture, anonymous 401 UNAUTHENTICATED, malformed 400 with the named code (rest-server.ts registerEmailEndpoints: enforceAuth, non-object→400 INVALID_REQUEST, VALIDATION_FAILED passthrough, 501 no-provider)", + "evidence": "the three responses + the dev-transport landing" + } + ], + "negative": [ + "the false-compliance case #4509 named is the standing FAIL: an admin 'fixes' a template and recipients keep receiving the old copy — any render not matching the authoritative row is a FAIL against the bridge", + "a send with an unresolved {{placeholder}} residue delivered to the transport is a FAIL (render must substitute or refuse, never ship holes)" + ], + "traps": [ + "stale-dist" + ], + "automated": { + "kind": "e2e", + "ref": "packages/qa/dogfood/test/email-template-materialization.dogfood.test.ts" + }, + "source": [ + "packages/spec/src/system/email-template.zod.ts (requireds, dotted-name regex, aliases #5013, variables/required, fromOverride/replyTo/active)", + "packages/spec/liveness/email_template.json (the whole surface live via #4509; per-prop evidence incl. requireVars, TEMPLATE_INACTIVE, seed-not-clobber, runtime re-materialization)", + "packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts + email-service.ts (row mapping; (name,locale)+fallback; the gates)", + "examples/app-showcase/src/system/emails/index.ts (showcase_task_done_email fixture)", + "packages/rest/src/rest-server.ts (registerEmailEndpoints — POST /api/v1/email/send: enforceAuth 401 UNAUTHENTICATED #3963, non-object→400 INVALID_REQUEST, VALIDATION_FAILED passthrough, 501 no-provider, 500 EMAIL_SEND_FAILED)", + "packages/rest/src/rest-route-ledger.ts (email family — POST /api/v1/email/send → client email.send)", + "packages/spec/src/api/error-code-ledger.zod.ts (EMAIL_SEND_FAILED under @objectstack/rest)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — email_template kind had no checklist coverage", + "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 2, + "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-08-08", + "change": "added the raw POST /api/v1/email/send route clause (authed → dev transport, anonymous → 401 UNAUTHENTICATED, malformed → 400 envelope, no-provider → 501) per PENDING-GAPS §D", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "integration-system.notify-inbox-delivery", + "title": "The flow notify node delivers to the recipient's inbox (sys_inbox_message + receipt), readable and markable over /notifications, recipient-scoped — with unimplemented channels dead-lettering, never faking delivery", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": [ + "admin (to reassign the task)", + "the assignee member (to read the inbox)", + "a third member (for the scoping negative)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the shipped fixture flow showcase_task_assigned_notify (notify node: topic 'task.assigned', recipients ['{record.assignee}'], channels ['inbox'], title/message/actionUrl — examples/app-showcase/src/automation/flows/index.ts)", + "MessagingServicePlugin installed (the showcase config requires the 'messaging' capability)", + "three personas with sessions: admin, the assignee, an unrelated member" + ], + "knownGaps": [ + "channels push/slack/teams/webhook have NO delivery implementation (#3197 — notification.zod.ts says the dispatcher dead-letters them, and the enum's 'in-app' spelling vs the implemented 'inbox' channel is a known naming drift); this item tests inbox only and records the dead-letter behavior as a negative, not as deliverable channels" + ] + }, + "variants": [ + "channel: inbox — implemented, always-on (this item's happy path)", + "channel: email — implemented via plugin-email (covered by email-template-render; not re-proven here)", + "channel: sms — implemented via service-sms (needs provider credentials; blocked on stock fixtures)", + "channels push/slack/teams/webhook — unimplemented, dead-letter (#3197): negative-only" + ], + "steps": [ + "as admin, reassign a showcase_task to the assignee persona (assignee != previous.assignee fires the flow trigger)", + "as the ASSIGNEE, GET /api/v1/notifications (route ledger: notifications.list); locate the new row (title 'New task assigned: ', the actionUrl deep link)", + "as the THIRD member, GET /api/v1/notifications and confirm the notification is absent", + "unauthenticated GET /api/v1/notifications; capture the 401", + "as the assignee, POST /api/v1/notifications/read with { ids: [<id>] }; re-list with ?read=false and confirm it dropped out; then POST /notifications/read/all", + "read the backing rows over the data API (sys_inbox_message + sys_notification_receipt keyed (notification_id, user_id, channel:'inbox')) to confirm the surface reflects storage", + "author a scratch flow copy whose notify node names channels ['push']; run it; capture where the message lands (dead-letter), and that NO inbox row was fabricated" + ], + "acceptance": [ + { + "clause": "the notify node delivers: one inbox row for the assignee carrying the flow's title/message/actionUrl, listed over GET /notifications for that user", + "oracle": "api", + "verify": "the listing contains the notification with the authored strings after the reassignment", + "evidence": "the listing response" + }, + { + "clause": "delivery is recipient-scoped — the third member's listing does NOT contain it (server-side scoping, not client filtering)", + "oracle": "api", + "verify": "the third member's authenticated listing omits the row", + "evidence": "both listings side by side" + }, + { + "clause": "mark-read is persisted per (notification, user, channel): POST /notifications/read flips the receipt; an unread-filtered re-list omits it; read/all clears the rest — and the receipts are real rows, not client state", + "oracle": "api", + "verify": "the receipt row exists after markRead; the ?read=false listing shrinks accordingly", + "evidence": "the mutation responses + receipt row read" + }, + { + "clause": "the surface gates correctly on BOTH sides: unauthenticated → 401; with the messaging service absent/unserveable the route answers the 501 capability-unavailable envelope (never a fabricated empty inbox from a stub)", + "oracle": "api", + "verify": "the anonymous 401; the 501 side may be cited from a minimal boot without the messaging plugin", + "evidence": "the two responses" + }, + { + "clause": "an unimplemented channel dead-letters instead of faking success: the ['push'] probe produces NO inbox row and the dispatcher records the dead-letter — declared-but-unimplemented channels must stay visible failures (#3197)", + "oracle": "log", + "verify": "no sys_inbox_message row for the probe run; the dead-letter/log evidence captured", + "evidence": "absence check + log excerpt" + } + ], + "negative": [ + "a notify step reporting success while the recipient's listing stays empty is a FAIL against the delivery chain (flow → messaging → inbox row → route), wherever it broke", + "the third member seeing another user's notification is a security FAIL (receipts and rows are per-user; the route must scope server-side)" + ], + "traps": [ + "wrong-persona", + "seed-data-thin" + ], + "source": [ + "packages/runtime/src/domains/notifications.ts (routes, auth:true, sys_inbox_message + sys_notification_receipt join, receipt key)", + "packages/runtime/src/route-ledger.ts (GET /notifications, POST /notifications/read, POST /notifications/read/all)", + "packages/spec/src/system/notification.zod.ts (channel enum + the #3197 implemented-vs-dead-letter note and 'in-app' vs 'inbox' drift)", + "packages/services/service-messaging/src/inbox-channel.ts + channel.ts (inbox delivery writes; channels default ['inbox'])", + "examples/app-showcase/src/automation/flows/index.ts (showcase_task_assigned_notify — the worked ADR-0012 notify fixture)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-07", + "change": "new item: the notify→inbox→/notifications chain had no checklist coverage; unimplemented channels pinned as dead-letter negatives per #3197 instead of asserted as capabilities", + "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "integration-system.external-datasource-federated-read", + "title": "A declared external datasource federates: its objects query in place over REST, read-only", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": [ + "admin" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the shipped read-only SQLite external datasource (examples/app-showcase/src/system/datasources/ — showcase-external.datasource.ts + external-fixture.ts, External Customer / External Order)" + ] + }, + "steps": [ + "boot showcase isolated; sign in as admin", + "GET /api/v1/meta and confirm the federated objects are registered", + "GET /api/v1/data/<external object> and read fixture rows", + "attempt a write (POST/PATCH) against the read-only external object; capture the refusal", + "open Setup → Datasources and confirm the connection is listed with its health" ], - "source": ["packages/spec/src/system/email-template.zod.ts", "packages/spec/liveness/email_template.json"], - "history": [{ "revision": 1, "date": "2026-08-07", "change": "initial — email_template kind had no checklist coverage", "ref": "claude/platform-test-checklist-ocwugl" }] + "acceptance": [ + { + "clause": "the external datasource's objects appear in /meta and return the fixture rows over the normal /data query path — federation is transparent to the query layer", + "oracle": "api", + "verify": "/data/<external object> returns the seeded external rows; filters/$top work as on a native object", + "evidence": "the reads" + }, + { + "clause": "writes to a read-only external object are refused with a located error, not silently dropped or half-applied", + "oracle": "api", + "verify": "POST/PATCH → 4xx naming the read-only datasource", + "evidence": "the refusal" + }, + { + "clause": "the connection is visible and health-badged in Setup → Datasources", + "oracle": "screenshot", + "verify": "the datasources admin page shows the external connection", + "evidence": "screenshot" + } + ], + "negative": [ + "a write that appears to succeed against a read-only external source is a FAIL" + ], + "traps": [ + "stale-dist" + ], + "source": [ + "examples/app-showcase/src/system/datasources/ (showcase-external.datasource.ts, external-fixture.ts)", + "content/docs/capabilities/integrations.mdx (federated datasource claim)", + "packages/runtime route-ledger external-datasource family" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — the coverage.json `datasource` waiver was STALE (showcase ships a read-only SQLite external fixture); un-waived. NOTE: the external-datasource ADMIN CRUD lifecycle is a separate item (datasource-admin-lifecycle)", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "integration-system.datasource-admin-lifecycle", + "title": "The /api/v1/datasources admin lifecycle: static driver catalog, runtime create with provenance+health, secret never echoes (hasSecret only), bad drafts 400 DATASOURCE_ADMIN_ERROR, unwired federation degrades 503 naming external-datasource", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": [ + "admin" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "os dev mounts the datasource admin routes at /api/v1/datasources by default (packages/cli/src/commands/serve.ts wires DatasourceAdminServicePlugin + registerDatasourceAdminRoutes when the engine is not 'memory')", + "the sqlite driver from the static catalog (packages/services/service-datasource/src/driver-catalog.ts) + a writable location for the sqlite file — the runtime-create probe target", + "a crypto provider / secret binder (createDatasourceSecretBinder) so the inline secret is bound, not stored cleartext" + ], + "knownGaps": [ + "the external-datasource FEDERATION service is intentionally NOT wired in the admin-lifecycle boot — its absence is what the 503-naming clause verifies; a boot that DOES wire it should record that and skip the 503 clause as not-applicable-this-run" + ] + }, + "steps": [ + "boot showcase via os dev (serve.ts mounts /api/v1/datasources by default on a non-memory engine); sign in as admin", + "GET /api/v1/datasources/drivers; confirm the static catalog (memory/sqlite/postgres/mysql/mongo, each with a configSchema) — this route has NO service dependency and answers even before any datasource-admin service is wired", + "POST /api/v1/datasources with a sqlite-file draft { name: 'qa_ds_probe', driver: 'sqlite', config: { file: '<writable path>' }, secret?: '...' }; capture status + the returned datasource", + "GET /api/v1/datasources; confirm qa_ds_probe appears with origin:'runtime' and a health field", + "GET /api/v1/datasources/qa_ds_probe; inspect the body for config + a hasSecret flag and confirm the cleartext secret value is ABSENT", + "POST /api/v1/datasources with a bad draft (invalid config shape / missing required); capture status + code", + "GET /api/v1/datasources/qa_ds_probe/remote-tables (an external-datasource-served route) on the boot with federation UNWIRED; capture the 503 + which service its message names", + "GET /api/v1/datasources/does-not-exist; capture the 404" + ], + "acceptance": [ + { + "clause": "the driver catalog is static and always-available: GET /api/v1/datasources/drivers returns the curated driver set (memory/sqlite/postgres/mysql/mongo) each with a projected configSchema, with NO datasource-admin service dependency", + "oracle": "api", + "verify": "the drivers body against DRIVER_CATALOG (driver-catalog.ts); route answers even when the admin service is unwired", + "evidence": "the drivers response" + }, + { + "clause": "a runtime create lands with provenance + health: POST /api/v1/datasources creates a sqlite-file datasource (201) and the subsequent list shows it with origin:'runtime' and a health status", + "oracle": "api", + "verify": "POST status 201; GET /api/v1/datasources contains qa_ds_probe with origin 'runtime' + a health field", + "evidence": "the create + list responses" + }, + { + "clause": "the secret NEVER echoes back: the create body's inline secret is split out server-side (splitSecret) so it never reaches the persisted draft, and every read (getDatasource is credential-stripped) exposes only a hasSecret boolean plus non-sensitive config — the cleartext secret is returned by no GET", + "oracle": "api", + "verify": "GET /api/v1/datasources/qa_ds_probe carries hasSecret + config but no secret value; a grep of the list + detail bodies finds the secret nowhere", + "evidence": "the detail + list bodies (secret-absent)" + }, + { + "clause": "a malformed draft is refused 400 DATASOURCE_ADMIN_ERROR — the datasource-admin service's registered refusal code (attributed to the service that refused, #4249), never a 500", + "oracle": "api", + "verify": "the bad-draft POST: status 400, error.code DATASOURCE_ADMIN_ERROR (registered under @objectstack/service-datasource)", + "evidence": "the 400 response" + }, + { + "clause": "an unwired federation service degrades 503 naming external-datasource: the introspection routes (/:name/remote-tables, /:name/test, /:name/object-draft) answer 503 SERVICE_UNAVAILABLE whose message names the external-datasource service — NOT datasource-admin (the #4225 mis-attribution the resolve() helper exists to prevent, since datasource-admin itself is running fine)", + "oracle": "api", + "verify": "the remote-tables 503 message names 'external-datasource', not 'datasource-admin'", + "evidence": "the 503 response" + }, + { + "clause": "unknown name → 404 RESOURCE_NOT_FOUND; and (FINDING) the /api/v1/datasources admin CRUD is UNLEDGERED — absent from packages/rest/src/rest-route-ledger.ts (only the /datasources/:name/external/* federation routes are ledgered there), a tranche-3 route-ledger discipline gap the run must record (PENDING-GAPS §E)", + "oracle": "api", + "verify": "GET /api/v1/datasources/does-not-exist → 404 RESOURCE_NOT_FOUND; run record notes the admin routes carry no route-ledger entry", + "evidence": "the 404 + the unledgered-mount finding" + } + ], + "negative": [ + "the cleartext secret appearing in ANY list/detail response is a security FAIL — hasSecret is the only permitted signal", + "a federation-route 503 that names datasource-admin (the service that IS running) instead of external-datasource is the #4225 mis-attribution regressed — FAIL", + "a datasource-admin refusal carrying EXTERNAL_DATASOURCE_ERROR (or an external-datasource refusal carrying DATASOURCE_ADMIN_ERROR) is the #4249 code mis-attribution — FAIL" + ], + "traps": [ + "dispatcher-vs-hono-route", + "stale-dist" + ], + "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" + }, + "source": [ + "packages/services/service-datasource/src/admin-routes.ts (the nine routes; splitSecret keeps the secret out of the persisted draft; resolve()/badRequest() per-service attribution #4225/#4249; getDatasource credential-stripped + hasSecret)", + "packages/services/service-datasource/src/driver-catalog.ts (static DRIVER_CATALOG, configSchema projected from spec #4410)", + "packages/spec/src/api/error-code-ledger.zod.ts (DATASOURCE_ADMIN_ERROR, EXTERNAL_DATASOURCE_ERROR under @objectstack/service-datasource)", + "packages/spec/src/api/errors.zod.ts (HttpStatusErrorCodeMap: 503 SERVICE_UNAVAILABLE, 404 RESOURCE_NOT_FOUND)", + "packages/cli/src/commands/serve.ts (mounts registerDatasourceAdminRoutes at /api/v1/datasources by default — NOT in the REST route ledger, tranche-3 gap)", + "PENDING-GAPS §B/§E (#4225/#4249; service-datasource has no route ledger)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "new — the external-datasource ADMIN lifecycle (driver catalog, runtime create + provenance/health, secret-never-echoes, 400/503 per-service attribution, unledgered-mount finding); distinct from external-datasource-federated-read (which tests the seeded read-only fixture's query path) — cross-referenced, not duplicated. Per PENDING-GAPS §B/§E (#4225/#4249)", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "integration-system.notification-preference-suppression", + "title": "A recipient preference muting the inbox channel for a topic suppresses delivery (no inbox row, no dead-letter fake); flipping it back resumes; a sys_notification_template renders through a delivery", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": [ + "admin (to fire the notify flow by reassigning the task)", + "the assignee member (whose inbox is checked)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "MessagingServicePlugin installed (the showcase config requires the 'messaging' capability) — its emit() consults the PreferenceResolver before fan-out", + "the shipped notify flow showcase_task_assigned_notify (notify node: topic 'task.assigned', channels ['inbox'] — examples/app-showcase/src/automation/flows/index.ts)", + "two personas with sessions: admin and the assignee" + ], + "knownGaps": [ + "no shipped sys_notification_preference rows nor a sys_notification_template fixture — both authored in-run (both are writable system-data objects); the notify flow fixture itself is shipped. The spec's channel enum spells this 'in-app'; the implemented channel id is 'inbox' (the #3197 naming drift) — the preference row must name the IMPLEMENTED id 'inbox'" + ] + }, + "steps": [ + "boot showcase isolated; personas admin + assignee; establish the happy path first (or lean on notify-inbox-delivery): reassign a showcase_task to the assignee → showcase_task_assigned_notify fires topic 'task.assigned' on channel 'inbox' → one sys_inbox_message for the assignee, visible in GET /api/v1/notifications", + "author a suppression row via POST /api/v1/data/sys_notification_preference: { user_id: <assigneeId>, topic: 'task.assigned', channel: 'inbox', enabled: false } — this is the 'category' the task names; the model's field is `topic` (most-specific-wins over the built-in default-ON)", + "re-fire the notify flow (reassign the task to the same assignee again); GET /api/v1/notifications as the assignee and confirm NO new inbox row landed", + "read the messaging outbox / dead-letter surface and confirm there is no dead-letter row for the muted (recipient, 'inbox') — a deliberate mute is a DROP by the preference filter, not a dead-letter (dead-letter is reserved for unimplemented channels, #3197)", + "flip the row enabled:true (or delete it); re-fire; confirm the inbox row is delivered again", + "probe the resolution order: a user_id='*' admin-global default OFF with a user-specific row ON delivers (user row overrides global); a mandatoryTopics-configured topic bypasses the matrix entirely (cannot be muted)", + "render a sys_notification_template through a delivery: author a scratch sys_notification_template keyed (topic, channel, locale) and drive a template-backed channel (email/sms — messaging-service-plugin registers them as 'renders sys_notification_template'); capture the rendered subject/body" + ], + "acceptance": [ + { + "clause": "a suppression row mutes the channel: with (assignee, 'task.assigned', 'inbox', enabled:false) present, firing the notify flow delivers NO inbox row to the assignee — the PreferenceResolver drops the recipient once no channel survives the filter (most-specific-wins; built-in default ON)", + "oracle": "api", + "verify": "post-fire GET /api/v1/notifications for the assignee lacks the new row; the preference row is present with enabled:false", + "evidence": "the empty-of-new listing + the preference row" + }, + { + "clause": "no dead-letter fake: the muted delivery is DROPPED by preference, not recorded as a dead-letter/failed row — dead-letter is reserved for unimplemented channels (#3197); a deliberate mute must leave no fake failure trace", + "oracle": "api", + "verify": "no sys_inbox_message for the muted fire AND no dead-letter row for the (recipient, 'inbox') pair", + "evidence": "the absence checks (inbox + dead-letter)" + }, + { + "clause": "flipping back resumes delivery: setting enabled:true (or removing the row) restores default-ON and the next fire delivers the inbox row again — the mute is reversible and reflects the CURRENT matrix, not a cached decision", + "oracle": "api", + "verify": "post-flip GET /api/v1/notifications shows the new inbox row", + "evidence": "the before/after listings around the flip" + }, + { + "clause": "resolution is most-specific-wins: a user-specific row overrides the user_id='*' admin-global default, and a mandatoryTopics-configured topic bypasses the matrix (cannot be muted) — the ADR-0030 Layer-3 precedence holds on the live pipeline", + "oracle": "api", + "verify": "the global-OFF + user-ON probe delivers; a mandatory-topic fire delivers despite an enabled:false row", + "evidence": "the two probe outcomes" + }, + { + "clause": "a sys_notification_template renders through a delivery: driving a template-backed channel resolves (topic, channel, locale) from sys_notification_template with locale fallback and renders the subject/body holes — proving the template surface is live and DISTINCT from the inline title/message the inbox flow uses", + "oracle": "log", + "verify": "the captured render carries the template's substituted subject/body (email/sms channel path; messaging-service-plugin 'renders sys_notification_template')", + "evidence": "the captured render" + } + ], + "negative": [ + "a suppression row present and enabled:false but an inbox row STILL landing is a FAIL — the preference filter was bypassed (distinct from the by-design fail-OPEN, which applies only to a preference OUTAGE: no data engine / lookup error keeps all channels; a HEALTHY lookup ignoring an enabled:false row is the FAIL)", + "distinct from notify-inbox-delivery (the inbox happy path) — a run must not double-count the happy path here; this item's proof is the SUPPRESSION and its reversal" + ], + "traps": [ + "wrong-persona", + "seed-data-thin", + "stale-dist" + ], + "source": [ + "packages/services/service-messaging/src/objects/notification-preference.object.ts (sys_notification_preference: user_id × topic × channel × enabled; '*' wildcards + admin-global default; unique (user_id,topic,channel) index)", + "packages/services/service-messaging/src/preference-resolver.ts (PreferenceResolver.filter — most-specific-wins, mandatory-topic bypass, fail-open; drops recipients left with no accepted channel)", + "packages/services/service-messaging/src/messaging-service.ts (emit() consults the PreferenceResolver before fan-out)", + "packages/services/service-messaging/src/messaging-service-plugin.ts (email/sms channels render sys_notification_template; shared retry/dead-letter substrate)", + "packages/spec/src/system/notification.zod.ts (#3197 dead-letter for unimplemented channels; 'in-app' vs 'inbox' naming drift)", + "examples/app-showcase/src/automation/flows/index.ts (showcase_task_assigned_notify — topic 'task.assigned', channels ['inbox'])" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "new — preference-driven inbox suppression (no row, no dead-letter fake), reversal, ADR-0030 Layer-3 precedence, and a sys_notification_template render; distinct from notify-inbox-delivery (happy path). Per PENDING-GAPS §C", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] } ] -} +} \ No newline at end of file diff --git a/docs/qa/platform-checklist/areas/platform-core.json b/docs/qa/platform-checklist/areas/platform-core.json index 3c792df7cb..8a9ef2f1a2 100644 --- a/docs/qa/platform-checklist/areas/platform-core.json +++ b/docs/qa/platform-checklist/areas/platform-core.json @@ -279,7 +279,7 @@ "title": "The metadata registry is served over REST: /meta lists every registered type with its spec-derived create seed", "since": "v15", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "api", "personas": ["seeded admin (admin@objectos.ai / admin123)"], @@ -290,7 +290,9 @@ "GET /api/v1/meta/types (the richer Studio listing — a distinct server-only route per packages/runtime/src/route-ledger.ts) and capture it", "GET /api/v1/meta/view/showcase_task and confirm the stored view item is served with its authored shape (list + listViews + formViews keys)", "GET /api/v1/meta/:type for 'view' and confirm the showcase's authored views are enumerated", - "run the pinned create-seed contract: pnpm --filter @objectstack/dogfood exec vitest run test/meta-types-create-seed.dogfood.test.ts" + "run the pinned create-seed contract: pnpm --filter @objectstack/dogfood exec vitest run test/meta-types-create-seed.dogfood.test.ts", + "object-extension overlay: GET /api/v1/meta/object/showcase_account and confirm the additive fields contributed by examples/app-showcase/src/data/extensions/account.extension.ts (loyalty_tier select bronze/silver/gold/platinum, linkedin_url url, csat_score number 0–100) are present in the MERGED object — the extension carries priority 210 and never re-declares showcase_account (defineObjectExtension, merged at registerApp)", + "render + round-trip the overlay: open a showcase_account record form and confirm the three extension fields render alongside the base fields; PATCH /api/v1/data/showcase_account/<id> setting loyalty_tier='gold' and csat_score=88, then GET the row and confirm both persisted (the overlay is a real column, not a display-only badge)" ], "acceptance": [ { @@ -322,10 +324,23 @@ "oracle": "api", "verify": "the listing contains the expected view names from examples/app-showcase/src/ui/views/", "evidence": "the listing" + }, + { + "clause": "an object extension merges ADDITIVELY into the served object: GET /api/v1/meta/object/showcase_account carries the account.extension.ts fields (loyalty_tier, linkedin_url, csat_score) merged in without re-declaring the object — the mechanism a package uses to extend an object it does not own", + "oracle": "api", + "verify": "the /meta/object response's fields include all three overlay fields with their declared types/options; showcase_account is authored once (defineObjectExtension priority 210 wins on conflict, merged at registerApp)", + "evidence": "the merged field inventory" + }, + { + "clause": "the overlay fields are real columns, not display-only: they render on the showcase_account form and a write to loyalty_tier + csat_score round-trips through /api/v1/data/showcase_account", + "oracle": "api", + "verify": "PATCH /api/v1/data/showcase_account/<id> {loyalty_tier:'gold', csat_score:88} then GET the row returns both values; a screenshot confirms the fields render on the form", + "evidence": "the PATCH + GET pair + the form screenshot" } ], "negative": [ "a registered type missing from GET /meta (or served without its registry contract) is a FAIL — the Studio designer derives its create UX from this response, and drift here produced real create-save 422s (the dashboard-layout / action-body incidents the pinned test memorializes)", + "an extension field present in account.extension.ts but ABSENT from GET /meta/object/showcase_account (or present in /meta but not writable through /data) is a FAIL — the overlay did not merge, and a package's additive fields would silently vanish", "an unauthenticated GET /api/v1/meta that returns the full registry is a finding for the access-security area — capture and cross-file it, do not tick past it" ], "traps": ["dispatcher-vs-hono-route"], @@ -333,10 +348,13 @@ "source": [ "packages/spec/src/kernel/metadata-plugin.zod.ts (MetadataTypeSchema + DEFAULT_METADATA_TYPE_REGISTRY)", "packages/spec/src/kernel/metadata-create-seeds.ts", - "packages/runtime/src/route-ledger.ts (GET /meta, GET /meta/types, GET /meta/:type, GET /meta/:type/:name)" + "packages/runtime/src/route-ledger.ts (GET /meta, GET /meta/types, GET /meta/:type, GET /meta/:type/:name)", + "examples/app-showcase/src/data/extensions/account.extension.ts (AccountExtension — additive overlay on showcase_account, priority 210)", + "packages/spec/src/data (defineObjectExtension — extend merges fields at registerApp, higher priority wins on conflict)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — pins the registry-serving contract the Studio create path depends on", "ref": "claude/platform-test-checklist-ocwugl" } + { "revision": 1, "date": "2026-08-07", "change": "initial — pins the registry-serving contract the Studio create path depends on", "ref": "claude/platform-test-checklist-ocwugl" }, + { "revision": 2, "date": "2026-08-08", "change": "added the object-extension-overlay clauses: account.extension.ts (Loyalty Tier / LinkedIn / CSAT) merges additively into /meta/object/showcase_account, renders on the form, and round-trips a write", "ref": "claude/platform-test-checklist-ocwugl" } ] }, { @@ -418,6 +436,585 @@ "history": [ { "revision": 1, "date": "2026-08-07", "change": "initial — grounds the Studio authoring pipeline end-to-end with both deny gates as first-class clauses", "ref": "claude/platform-test-checklist-ocwugl" } ] + }, + { + "id": "platform-core.builtin-apps-nav-render", + "title": "The three built-in apps (Setup / Studio / Account) render every merged-nav destination; app-level and entry-level gates are enforced, not errored", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P0", + "surface": "browser", + "personas": [ + "seeded admin (admin@objectos.ai / admin123)", + "a plain member (fresh runtime sign-up — lands in member_default, holds neither setup.access nor studio.access)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the three platform apps ship with @objectstack/platform-objects and register as one-app packages @objectstack/{setup,studio,account} (ADR-0048), so their ids are com.objectstack.setup / com.objectstack.studio / com.objectstack.account — present on any stock boot, no showcase-specific fixture", + "a second, non-admin persona: sign up a fresh user in-run to drive the app-level and entry-level permission gates (do not reuse the admin — wrong-persona masks the guard)" + ], + "knownGaps": [ + "the SSO Providers entry (nav_sso_providers → sys_sso_provider) is contributed ONLY when the external-IdP RP is wired (OS_SSO_ENABLED self-host / cloud planAllowsSso — plugin-auth auth-plugin.ts isSsoWired()). On a stock open-framework boot it is ABSENT by design (not gated-and-erroring); assert its absence, never treat it as a missing surface" + ] + }, + "steps": [ + "as admin, GET /api/v1/meta/app?id=com.objectstack.setup and extract every merged nav destination — Setup is a SHELL of empty group anchors (packages/platform-objects/src/apps/setup.app.ts: group_overview/apps/people_org/access_control/approvals/configuration/diagnostics/integrations/advanced) filled by SETUP_NAV_CONTRIBUTIONS (setup-nav.contributions.ts) plus capability-plugin contributions (plugin-security Positions/Permission Sets, plugin-sharing Sharing Rules/Record Shares, plugin-approvals, plugin-webhooks)", + "GET /api/v1/meta/app?id=com.objectstack.studio (studio.app.ts — static nav: Overview, Data Model, User Experience, Logic, Automation, AI, Developer, Integration) and GET /api/v1/meta/app?id=com.objectstack.account (account.app.ts — Profile + Inbox/Security/Developer groups)", + "as admin, hand-walk each destination in each app: navigate, wait for render, SCREENSHOT FIRST, then read the DOM — assert no pageerror, no 'failed to load', no blank <main>, no 'no actions configured' placeholder", + "Setup detail: confirm each settings URL entry (nav_settings_* → /apps/setup/system/settings/<namespace>) opens the settings namespace form (localization/company/branding/auth/mail/storage/ai/knowledge/feature_flags), and Users / Organization / Business Units / Teams / Sessions / OAuth Applications / Identity Links / User Preferences render", + "Studio detail: confirm each metadata:resource list (object/app/view/page/dashboard/report/dataset/action/hook/flow/agent/tool/skill/email_template) renders, and the component surfaces render (App Builder studio:builder, All Metadata Types metadata:directory, Packages developer:packages, API Console developer:api-console, Flow Runs developer:flow-runs, Public Forms developer:public-forms)", + "Account detail: confirm Profile (account:profile_card), Notifications (sys_inbox_message/mine), Approvals (sys_approval_request/my_pending), Linked Accounts (sys_account), Active Sessions (sys_session/mine), API Keys (sys_api_key/mine), OAuth Applications (sys_oauth_application/mine) each render", + "confirm the gated entries resolve to ABSENT-not-erroring for the admin: nav_organizations (requiresService org-scoping) is absent in single-org mode; nav_jwks Signing Keys (requiredPermissions manage_platform_settings, sys_jwks private per ADR-0066) is PRESENT for admin; SSO Providers is absent unless OS_SSO_ENABLED (knownGap)", + "sign in as the plain member: attempt to open Setup (com.objectstack.setup) and Studio (com.objectstack.studio) — capture the app-level refusal (App.requiredPermissions setup.access / studio.access); open Account (declares no requiredPermissions) — capture it opening", + "as the member, GET /api/v1/meta/app for a reachable app and confirm every manage_platform_settings-gated entry (JWKS, API Keys, the manage_platform_settings settings URLs) is ABSENT from the member's payload — the server prunes, the client does not merely hide", + "cross-check served-vs-rendered: diff each app's meta/app nav destinations against what actually rendered so nothing is silently outside the walk" + ], + "acceptance": [ + { + "clause": "every merged-nav destination in all three apps renders a real surface for the admin — no pageerror, no 'failed to load', no blank <main>, no placeholder leak; this closes the gap platform-core.nav-surfaces-render leaves (it only sweeps the showcase app)", + "oracle": "screenshot", + "verify": "per-destination screenshot (screenshot-first) then DOM mark-check, across Setup + Studio + Account", + "evidence": "per-app per-destination screenshot set + verdict table" + }, + { + "clause": "the served merged nav matches each app's authored shell + contributions: Setup's group anchors (setup.app.ts) are filled by SETUP_NAV_CONTRIBUTIONS and capability plugins; Studio's static groups (studio.app.ts) are all present; Account's Profile/Inbox/Security/Developer groups present", + "oracle": "api", + "verify": "the three GET /meta/app payloads list the expected group ids + entries per the app source files", + "evidence": "the three nav payloads" + }, + { + "clause": "as a plain member, Setup and Studio REFUSE (app-level requiredPermissions setup.access / studio.access) with a named access-denied surface — never a blank shell — while Account OPENS (declares no requiredPermissions, RLS scopes its rows)", + "oracle": "screenshot", + "verify": "run the two refusals + the Account open as the member persona (wrong-persona trap — do it as the member, not the admin)", + "evidence": "three screenshots" + }, + { + "clause": "the app-level gate is server-side, not a client courtesy: a forged member GET /api/v1/meta/app?id=com.objectstack.setup is denied/empty at the server, not merely hidden in the launcher (ADR-0057 D10 both-sides)", + "oracle": "api", + "verify": "the forged request's status/body proves server-side denial", + "evidence": "the forged response" + }, + { + "clause": "permission-gated ENTRIES are absent from the member's merged nav, not present-and-erroring: JWKS (nav_jwks), API Keys (nav_api_keys) and the manage_platform_settings settings entries do not appear in the member's payload", + "oracle": "api", + "verify": "diff of admin vs member /meta/app nav destinations — the gated entries are only in the admin set", + "evidence": "the admin-vs-member diff" + }, + { + "clause": "service/object-gated entries resolve to ABSENCE on stock fixtures rather than an entry that can only error: nav_organizations (requiresService org-scoping) absent in single-org mode; nav_business_units (requiresObject sys_business_unit) present only when the object is registered", + "oracle": "api", + "verify": "the gated-entry presence in the served payload matches the requiresService/requiresObject conditions in setup-nav.contributions.ts / account.app.ts", + "evidence": "the gated-entry presence table" + }, + { + "clause": "an off-capability destination is PRUNED from the nav, never rendered as a dead 'failed to load' list — the setup-nav.contributions.ts rationale (sys_verification/sys_device_code omit list; SSO absent unless wired) holds at runtime", + "oracle": "screenshot", + "verify": "the absent entries do not appear; SSO Providers absent on stock boot per the knownGap", + "evidence": "absence confirmation + the knownGap note" + } + ], + "negative": [ + "a built-in app destination that renders a blank <main> or a 'failed to load' / 'no actions configured' placeholder with no error boundary is a FAIL (screenshot-first rules out transitional emptiness, then the persistent blank is the finding)", + "the member reaching Setup or Studio content (app gate bypassed) is a FAIL — App.requiredPermissions is a server contract, prove denial on the wire, not just a hidden launcher tile", + "a permission-gated entry rendered for the member and then erroring on click is a FAIL of the 'absent not erroring' contract — the nav must prune server-side" + ], + "traps": ["hydration-race", "wrong-persona", "stale-console-bundle"], + "source": [ + "packages/platform-objects/src/apps/setup.app.ts (shell group anchors + requiredPermissions setup.access)", + "packages/platform-objects/src/apps/studio.app.ts (static nav; requiredPermissions studio.access)", + "packages/platform-objects/src/apps/account.app.ts (no requiredPermissions; hidden from App Switcher; requiresObject/requiresService entry gates)", + "packages/platform-objects/src/apps/setup-nav.contributions.ts (nav_jwks manage_platform_settings; nav_organizations requiresService org-scoping; nav_api_keys manage_platform_settings)", + "packages/plugins/plugin-auth/src/auth-plugin.ts (SSO Providers nav_sso_providers contributed only when isSsoWired())", + "ADR-0048 (Setup/Studio/Account as one-app packages com.objectstack.{setup,studio,account}); ADR-0029 (nav contributions); ADR-0066 (sys_jwks private)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "initial — the big built-in-apps nav sweep (Setup/Studio/Account merged nav render + app-level and entry-level gates both-sides), complementing nav-surfaces-render which only walks the showcase app", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "platform-core.settings-hub-roundtrip", + "title": "Settings hub round-trip: a value saves, PERSISTS, reaches an observable consumer, audits, env-locks, tests honestly, and stores secrets as handles — anonymous denied", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": [ + "seeded admin (admin@objectos.ai / admin123 — holds setup.access/setup.write/manage_platform_settings)", + "anonymous" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the 10 open-framework settings namespaces registered by @objectstack/service-settings (packages/services/service-settings/src/manifests: localization, company, branding, auth, mail, sms, storage, ai, knowledge, feature_flags)", + "the settings service wired with a data engine so it swaps from in-memory to the sys_setting table, and with the sys_secret store + auditWriter (SettingsServicePlugin binds these on kernel:ready; the showcase stack mounts the engine)" + ], + "knownGaps": [ + "HONORING the ai.json caveat: a saved settings FORM is NOT proof of effect — the AI namespace's test action is a stub validator until @objectstack/service-ai is mounted, and a saved ai.provider does nothing observable without it. The effect clause therefore uses localization, whose consumer (resolve-authz-context.ts reads localization.timezone/locale/currency onto every ExecutionContext) is stock and observable; branding.workspace_name in the shell is the browser fallback. Never tick effect off a persisted form." + ] + }, + "steps": [ + "as admin, GET /api/settings — capture the visible manifests (the 10 open-framework namespaces above; the route is the SettingsService REST surface in settings-routes.ts)", + "pick localization (an OBSERVABLE-CONSUMER namespace — packages/core/src/security/resolve-authz-context.ts reads localization.timezone/locale/currency onto ExecutionContext). GET /api/settings/localization — capture {manifest, values}; note timezone value 'UTC' with source 'default'", + "change it: PUT /api/settings/localization { timezone: 'Asia/Tokyo' }; re-read GET /api/settings/localization and confirm the value persists and source FLIPS default→tenant (localization scope is 'tenant')", + "prove EFFECT (not just the saved form): re-derive an ExecutionContext-dependent output — an analytics date-bucket aggregate or a today()/rendered-datetime on showcase_task — and confirm it now resolves against Asia/Tokyo, not UTC. If the deployment surfaces no observable localization effect, fall back to branding.workspace_name visible in the console shell — but never tick effect off the form alone", + "audit: GET /api/v1/data/sys_setting_audit filtered to namespace=localization,key=timezone — a row exists with action='set', source='api', actor_id=<admin>, new_hash set (SettingsServicePlugin.buildAuditWriter → sys_setting_audit)", + "source badge: open /apps/setup/system/settings/localization and confirm the timezone field's source indicator flips from default to the persisted scope after save; screenshot", + "env-lock: set OS_LOCALIZATION_TIMEZONE=Europe/Paris (envKeyOf convention: OS_<NAMESPACE>_<KEY>, settings-service.types.ts) and restart; GET /api/settings/localization now reports timezone source='env', locked=true, lockedReason 'Set via env: OS_LOCALIZATION_TIMEZONE', and the console renders the EnvLockBadge; PUT /api/settings/localization { timezone: 'UTC' } is REFUSED 409 SETTINGS_LOCKED (server-side, effectiveEnvOverride)", + "test action at the dev transport: POST /api/settings/mail/test with provider=log (or no email plugin mounted) — the handler answers ok:false and NO mail is faked (mail.manifest.ts mailTestActionHandler / plugin-email honest degradation, framework#5087)", + "secret handling: on a namespace with an encrypted specifier (mail.smtp_password or ai.*_api_key — type 'password' or encrypted:true), PUT a value; then read sys_secret + sys_setting — the ciphertext lands in sys_secret keyed by a 'sec_' handle and sys_setting.value_enc holds the handle id, NOT plaintext; GET /api/settings never returns the plaintext", + "anonymous deny: GET /api/settings/localization with no session → 403 SETTINGS_FORBIDDEN (assertPermitted read); GET /api/settings (list) as anon returns an EMPTY manifest set — zero namespaces enumerated (Finding-1 secure default)", + "guardrails: PUT /api/settings/localization { timezone: 'Mars/Phobos' } → 400 SETTINGS_VALIDATION invalid_option (declared options table); PUT { bogus_key: 1 } → 400 UNKNOWN_KEY" + ], + "acceptance": [ + { + "clause": "a settings value round-trips: PUT /api/settings/localization persists and GET re-reads it, with source FLIPPING default→tenant (the scope), not staying 'default'", + "oracle": "api", + "verify": "the PUT + GET responses; the timezone value is Asia/Tokyo and source is the scope, not 'default'", + "evidence": "the PUT + GET bodies with the source field" + }, + { + "clause": "EFFECT, not just a form: the saved value reaches its consumer — localization.timezone resolves onto ExecutionContext (resolve-authz-context.ts), so an ExecutionContext-dependent output (analytics date bucket / today() / rendered datetime) shifts from UTC to the saved zone", + "oracle": "api", + "verify": "the before/after output pair differs by exactly the zone change; NOTE (ai.json caveat) this clause requires an observable consumer — only localization/branding/auth qualify on stock fixtures, a persisted form is NOT proof", + "evidence": "the before/after consumer output" + }, + { + "clause": "every write appends a sys_setting_audit row (namespace/key/scope/action='set'/source='api'/actor_id/new_hash)", + "oracle": "api", + "verify": "GET /api/v1/data/sys_setting_audit shows the row for localization.timezone (SettingsServicePlugin.buildAuditWriter)", + "evidence": "the audit row" + }, + { + "clause": "the console source badge flips from default to the persisted-scope source after save", + "oracle": "screenshot", + "verify": "before/after field screenshots of the localization timezone source indicator", + "evidence": "the two screenshots" + }, + { + "clause": "an OS_*-env-pinned key is server-authoritative: GET reports source='env' locked=true, and PUT is REFUSED 409 SETTINGS_LOCKED — the write refusal is the SERVER's, not the UI's", + "oracle": "api", + "verify": "the locked GET (source='env', lockedReason names OS_LOCALIZATION_TIMEZONE) + the 409 on the write (effectiveEnvOverride)", + "evidence": "the locked GET + the 409" + }, + { + "clause": "the env-pinned field renders the EnvLockBadge and is non-editable in the console", + "oracle": "screenshot", + "verify": "screenshot of the locked field with the badge", + "evidence": "the screenshot" + }, + { + "clause": "a declared test action does not fake success: POST /api/settings/mail/test with no deliverable transport answers ok:false (400 SETTINGS_ACTION_FAILED envelope) naming that no mail was sent", + "oracle": "api", + "verify": "the action response body (ok:false, message names the honest non-delivery — framework#5087)", + "evidence": "the action response" + }, + { + "clause": "an encrypted specifier's value lands in sys_secret as a handle, never plaintext: sys_setting.value_enc holds a 'sec_' handle id and sys_secret holds the ciphertext; GET /api/settings never echoes the plaintext", + "oracle": "api", + "verify": "the sys_secret row (id starts 'sec_', ciphertext present) + sys_setting.value_enc = that handle + the redacted GET (materialiseRow dereferences through sys_secret)", + "evidence": "the sys_secret row + sys_setting.value_enc + the redacted GET" + }, + { + "clause": "anonymous is denied: GET /api/settings/:namespace → 403 SETTINGS_FORBIDDEN, and GET /api/settings lists ZERO namespaces for an unauthenticated caller (no enumeration — Finding-1)", + "oracle": "api", + "verify": "the two anonymous responses", + "evidence": "the 403 + the empty list" + }, + { + "clause": "each of the 10 open-framework namespaces resolves GET /api/settings/:ns with its manifest + values — no namespace 500s or serves an empty manifest", + "oracle": "api", + "verify": "one GET per variant in variants[]; each returns {manifest, values} with the manifest's specifiers", + "evidence": "the per-namespace responses" + } + ], + "negative": [ + "a saved settings form treated as proof of effect (no observable consumer checked) is the ai.json anti-pattern — ticking the effect clause on the form alone is a FALSE PASS", + "a 200 on an env-locked PUT is a FAIL (the lock is a server contract, effectiveEnvOverride)", + "an encrypted value returned as plaintext by GET /api/settings, or stored inline in sys_setting.value rather than sys_secret, is a FAIL", + "a test action answering ok:true for a send that did not happen is a FAIL (framework#5087)", + "GET /api/settings enumerating namespaces for an anonymous caller is a FAIL (Finding-1 — the old default trusted x-user-id/x-permissions headers)" + ], + "variants": [ + "localization", + "company", + "branding", + "auth", + "mail", + "sms", + "storage", + "ai", + "knowledge", + "feature_flags" + ], + "traps": ["stale-console-bundle", "dispatcher-vs-hono-route"], + "source": [ + "packages/services/service-settings/src/settings-routes.ts (GET/PUT /api/settings, POST :ns/:actionId; 403 SETTINGS_FORBIDDEN / 409 SETTINGS_LOCKED / 400 SETTINGS_VALIDATION|UNKNOWN_KEY mapping; secure anonymous default)", + "packages/services/service-settings/src/settings-service.ts (cascade source default→scope; effectiveEnvOverride lock; encrypted→sys_secret handle via cryptoProvider+secretStore; validatePatch invalid_option)", + "packages/services/service-settings/src/settings-service-plugin.ts (verifiedContextFromRequest fail-closed; buildAuditWriter→sys_setting_audit; buildSecretStore→sys_secret; LocalCryptoProvider)", + "packages/services/service-settings/src/settings-service.types.ts (envKeyOf OS_<NAMESPACE>_<KEY>)", + "packages/services/service-settings/src/manifests/{localization,mail,ai,branding}.manifest.ts", + "packages/core/src/security/resolve-authz-context.ts (localization timezone/locale/currency → ExecutionContext — the observable consumer)", + "framework#5087 (a test action must not fake success), #5204 (env override enforcement)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "initial — settings hub round-trip with persistence + observable-consumer effect (honoring the ai.json form-is-not-effect caveat), sys_setting_audit, env-lock server refusal, honest test action, sys_secret handle-not-plaintext, anonymous deny", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "platform-core.package-lifecycle-enable-disable", + "title": "Package lifecycle: disable stops serving, enable restores, uninstall de-registers, commits grow per publish and revert restores the prior published shape", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { + "app": "showcase", + "requires": [ + "a runtime-created WRITABLE probe package — the SAME fixture platform-core.metadata-authoring-roundtrip creates via POST /api/v1/packages (reuse it, or create one in-run); author an object + a view + a nav entry into it and publish so it actually serves objects/nav/routes. The showcase's own code package is read-only by design (ADR-0070), so it cannot be the probe" + ], + "knownGaps": [ + "if the deployment blocks runtime package install/enable (e.g. a locked-down prod profile), record blocked(fixture) — the lifecycle routes still exist (route-ledger.ts) but there is nothing writable to drive them against" + ] + }, + "steps": [ + "create or reuse the writable probe package (POST /api/v1/packages); author a probe object + view + nav entry into it and publish (POST /api/v1/packages/:id/publish or /publish-drafts). Record its id", + "confirm it serves: GET /api/v1/meta lists the probe object; GET /api/v1/data/<probe_object> answers 200; the probe nav appears in the relevant /api/v1/meta/app", + "disable: PATCH /api/v1/packages/:id/disable → capture; then GET /api/v1/data/<probe_object> and the probe routes → now 404/403 (stopped serving); /meta/app no longer lists its nav", + "enable: PATCH /api/v1/packages/:id/enable → capture; the object/nav/routes serve again (restore)", + "commits grow per publish: GET /api/v1/packages/:id/commits before and after a second publish — the list grows by exactly one entry; capture both listings", + "revert restores the prior published shape: change the probe (e.g. add a field) and publish, then POST /api/v1/packages/:id/commits/:commitId/revert (or POST /api/v1/packages/:id/revert) → GET the /published shape and confirm it is back to the prior commit", + "uninstall: DELETE /api/v1/packages/:id → capture; GET /api/v1/meta no longer lists the probe object/kinds and GET /api/v1/packages no longer lists the package", + "both-sides deny: attempt PATCH /disable and DELETE against a READ-ONLY code package (a platform package or the showcase's own) → refused server-side (ADR-0070); capture the refusal" + ], + "acceptance": [ + { + "clause": "disable STOPS serving: after PATCH /packages/:id/disable, the package's objects/routes answer 404/403 and /meta/app drops its nav — disable is an enforcement, not a cosmetic flag", + "oracle": "api", + "verify": "GET /api/v1/data/<probe_object> and the probe routes return 404/403 after disable (were 200 before)", + "evidence": "before/after reads" + }, + { + "clause": "enable RESTORES: PATCH /packages/:id/enable and the same objects/nav/routes serve again", + "oracle": "api", + "verify": "the post-enable reads return 200 and the nav is back", + "evidence": "before/after reads" + }, + { + "clause": "uninstall DE-REGISTERS: DELETE /packages/:id, then GET /meta no longer lists its kinds and GET /packages no longer lists it", + "oracle": "api", + "verify": "the meta + packages listings after DELETE", + "evidence": "the two listings" + }, + { + "clause": "the commits list GROWS per publish: GET /packages/:id/commits gains exactly one entry per publish", + "oracle": "api", + "verify": "commit count after the second publish == count before + 1", + "evidence": "the two commit listings" + }, + { + "clause": "revert RESTORES the prior published shape: POST /packages/:id/commits/:commitId/revert (or /revert) returns the published metadata to the prior commit, and a subsequent /published read matches the pre-change shape", + "oracle": "api", + "verify": "the pre-change /published read equals the post-revert /published read", + "evidence": "the pre/post published reads" + }, + { + "clause": "the disabled package's surface is gone from the console — its nav entry is absent and a stale deep-link to its object shows a named not-found inside the shell, never a dead white page", + "oracle": "screenshot", + "verify": "screenshot the console after disable (nav absent) and a stale deep-link (named not-found in the shell)", + "evidence": "the two screenshots" + }, + { + "clause": "DENY side: PATCH /disable and DELETE against a read-only code package are refused server-side (ADR-0070), not silently applied", + "oracle": "api", + "verify": "the refusal status/code on the read-only package", + "evidence": "the refusal" + }, + { + "clause": "state is coherent across the cycle: a disabled-then-enabled package's DATA rows survive the toggle (disable stops serving, it does not destroy rows)", + "oracle": "api", + "verify": "row count before disable == row count after re-enable", + "evidence": "the two counts" + } + ], + "negative": [ + "a disabled package still serving its objects/routes (200 on /data/<probe_object>) is a FAIL — disable is an enforcement, not a flag", + "uninstall leaving orphaned metadata in /meta (or the package still in GET /packages) is a FAIL", + "a revert that does not restore the prior published shape, or a commits list that grows without a working revert, is a FAIL", + "a 2xx on the read-only-package disable/uninstall attempt is a FAIL of the ADR-0070 gate" + ], + "traps": ["dispatcher-vs-hono-route", "stale-console-bundle"], + "source": [ + "packages/runtime/src/route-ledger.ts (PATCH /packages/:id/enable|disable, DELETE /packages/:id, POST /packages/:id/publish|publish-drafts, GET /packages/:id/commits, POST /packages/:id/commits/:commitId/revert, POST /packages/:id/revert)", + "platform-core.metadata-authoring-roundtrip (the writable probe package this reuses)", + "ADR-0070 (writable vs read-only packages), ADR-0033 (drafts/publish/commits)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "initial — package enable/disable/uninstall + commits/revert lifecycle, driven against the writable probe package the authoring round-trip already creates", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "platform-core.notification-center", + "title": "Notification center (bell / InboxPopover): badge counts distinct unread topics + pending approvals, repeats coalesce, per-group and global mark-read drop the badge", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { + "app": "showcase", + "requires": [ + "a recurring notification that produces many identical rows (the showcase_scheduled_digest firing once a minute — the exact flood inboxGrouping.ts was written for), so the topic-coalescing is exercised rather than assumed", + "at least one pending approval request (sys_approval_request / my_pending) so the approvals portion of the badge and the Approvals tab have something to show" + ], + "knownGaps": [ + "rows without a notification_id (legacy/synthetic inbox rows) flip read optimistically but do NOT persist server-side — read-state lives in sys_notification_receipt keyed by the L2 event id (ADR-0030), so only rows carrying a notification_id survive reload. Verify persistence on a keyed row, and record synthetic rows as expected-non-persistent" + ] + }, + "steps": [ + "open the console as admin; the header (AppHeader.tsx) polls GET /api/v1/notifications?view=mine — capture the initial inbox rows", + "read the bell badge: it must equal distinct unread (topic,title) TOPICS + pending approvals, NOT raw row count (#2765) — the recurring digest producing many identical rows must not inflate it to 9+ off one topic", + "cross-check: count distinct unread (topic,title) groups from the payload (groupNotifications in inboxGrouping.ts) + pendingApprovalsCount and confirm it equals the rendered totalBadge", + "open the popover; confirm THREE tabs render: Notifications / Approvals / Activity", + "confirm coalescing: repeats of the same (topic,title) collapse into ONE expandable group with a ×N count pill; click the chevron to expand and reveal the members", + "per-group mark-read: click a group's 'Mark read' → capture POST /api/v1/notifications/read {ids:[...]} on the wire (ONE request for the whole group, not N); the group's unread dot clears and the badge drops by one topic", + "global mark-all-read: click 'Mark all read' → capture POST /api/v1/notifications/read/all; the notifications portion of the badge drops to 0; the approvals portion is unaffected", + "Approvals tab: confirm it lists/points at the pending approval request(s) and pendingApprovalsCount equals the sys_approval_request my_pending count", + "persistence: reload; read-state persists for rows carrying a notification_id (sys_notification_receipt upsert, ADR-0030); synthetic rows without one flip only optimistically (knownGap)" + ], + "acceptance": [ + { + "clause": "the badge counts distinct unread TOPICS + pending approvals, not raw rows — a recurring digest producing N identical rows contributes 1 to the badge (#2765)", + "oracle": "network", + "verify": "distinct-(topic,title) unread group count from the GET /notifications payload + pendingApprovalsCount == the rendered totalBadge", + "evidence": "the payload + the badge value" + }, + { + "clause": "three tabs render (Notifications / Approvals / Activity)", + "oracle": "screenshot", + "verify": "the popover shows all three TabsTrigger surfaces", + "evidence": "popover screenshot" + }, + { + "clause": "(topic,title) repeats COALESCE into one expandable group with a ×N pill; expanding reveals the members (a group of one renders as a plain row, no pill)", + "oracle": "dom", + "verify": "after the screenshot confirms the popover rendered, assert the group row carries the ×N pill and the expanded list holds N members", + "evidence": "collapsed + expanded screenshots + DOM excerpt" + }, + { + "clause": "per-group mark-read issues POST /api/v1/notifications/read {ids} as a SINGLE request for the whole group (not one per row), and the group's unread state clears", + "oracle": "network", + "verify": "the captured POST carries the group's member ids in one body; the badge drops by one topic", + "evidence": "the captured POST + before/after badge" + }, + { + "clause": "global mark-all-read issues POST /api/v1/notifications/read/all and drops the notifications portion of the badge to 0", + "oracle": "network", + "verify": "the captured POST + the badge's notifications portion at 0 afterward", + "evidence": "the captured POST + badge" + }, + { + "clause": "the Approvals tab lists the pending request(s) and pendingApprovalsCount equals the sys_approval_request my_pending count", + "oracle": "dom", + "verify": "the tab content + the count vs a direct sys_approval_request my_pending query", + "evidence": "the tab + the count comparison" + }, + { + "clause": "read-state PERSISTS across reload for rows carrying a notification_id (sys_notification_receipt upsert, ADR-0030) — after mark-read + reload the keyed row stays read; a mark-read that reverts on the next poll is the pre-ADR-0030 receipt bug", + "oracle": "network", + "verify": "the post-reload GET /notifications shows the keyed row still read", + "evidence": "the post-reload payload" + } + ], + "negative": [ + "a badge showing raw unread ROW count (inflating to 9+ off one recurring topic) is the #2765 regression — FAIL", + "mark-read firing one POST per row for a coalesced group (instead of one {ids} request) is a FAIL of the coalescing contract", + "read-state reverting to unread on the next poll for a keyed row (writing sys_notification_receipt through the generic data API, which rejects it — ADR-0103) is a FAIL", + "cross-ref: this is distinct from i18n.notification-localized-and-clears (single-entry localization + clear) and approvals.notification-deep-link (deep-link) — do not double-count their coverage here" + ], + "traps": ["hydration-race", "single-datapoint"], + "source": [ + "objectui packages/app-shell/src/layout/InboxPopover.tsx (totalBadge = unreadTopics + pendingApprovalsCount; three tabs; per-group markGroupRead + global onMarkAllRead)", + "objectui packages/app-shell/src/layout/inboxGrouping.ts (groupNotifications — (topic,title) coalescing, #2765)", + "objectui packages/app-shell/src/layout/AppHeader.tsx (postMarkRead → POST /api/v1/notifications/read[/all]; poll GET /notifications?view=mine)", + "packages/runtime/src/route-ledger.ts (GET /notifications, POST /notifications/read, POST /notifications/read/all)", + "packages/runtime/src/domains/notifications.ts (markRead upserts sys_notification_receipt; empty/mis-keyed ids → 400)", + "ADR-0030 (sys_inbox_message L5 materialization + sys_notification_receipt); ADR-0103 (receipt object api-method lockdown)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "initial — bell/InboxPopover: topic-based badge (#2765), (topic,title) coalescing, per-group + global mark-read on the wire, approvals tab, receipt persistence; cross-referenced with i18n + approvals notification items", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "platform-core.shell-nav-personalization", + "title": "Shell nav personalization: sidebar collapse, pin/reorder, favorites, recents and header breadcrumbs each persist and stay navigable across reload", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "fixtures": { + "app": "showcase", + "requires": [ + "stock showcase nav (multiple objects/dashboards/reports so there is something to pin, reorder, favorite, and visit as recents)" + ] + }, + "steps": [ + "sidebar collapse: toggle the sidebar to icon (collapsed) mode; confirm nav items still NAVIGATE in icon mode; reload — the collapsed state persists (cookie sidebar_state, packages/components/src/ui/sidebar.tsx SIDEBAR_COOKIE_NAME)", + "nav pin/unpin: pin a nav item (UnifiedSidebar → useNavPins.togglePin → favorite id nav:<navId>, backed by FavoritesProvider/UserDataAdapter); it appears in the Pinned section; reload — the pin persists (syncs via UserDataAdapter; migrates the legacy objectui-nav-pins key); unpin removes it", + "drag-reorder: reorder nav items by drag (UnifiedSidebar useNavOrder, enableReorder / onReorder=handleReorder); reload — the order persists (localStorage per-app key)", + "favorites star/unstar: star an object/record as a favorite (useFavorites / FavoritesProvider); it appears in favorites; reload — persists; unstar removes it", + "recents rail: visit several records/objects/dashboards; the sidebar Recent section (collapsed by default, top 5 — useTrackRouteAsRecent → RecentItemsProvider, localStorage objectui-recent-items scoped by userId, hydrates UserDataAdapter) lists them; reload — recents survive", + "header record-trail breadcrumbs: on a record detail the AppHeader breadcrumb shows the app/section/record trail; click a crumb to navigate back to its level; reload — the trail rebuilds from the route", + "record which persistence each feature uses: pins/favorites/recents are UserDataAdapter-backed (cross-device sync), reorder + collapse are local (localStorage / cookie)" + ], + "acceptance": [ + { + "clause": "collapsing the sidebar to icon mode still NAVIGATES — clicking an icon routes correctly — and the collapsed state survives reload (cookie sidebar_state)", + "oracle": "screenshot", + "verify": "collapsed screenshot + a nav click that routes + the collapsed state present after reload", + "evidence": "collapsed screenshot + post-reload state + the routed click" + }, + { + "clause": "pin/unpin: a pinned nav item appears in the Pinned section and survives reload; unpin removes it (useNavPins → nav:<navId> favorite)", + "oracle": "dom", + "verify": "after a screenshot confirms render, assert the pinned item present pre- and post-reload; absent after unpin", + "evidence": "pre/post-reload DOM" + }, + { + "clause": "drag-reorder persists: the reordered nav order is restored after reload (useNavOrder localStorage)", + "oracle": "dom", + "verify": "the nav item order before reload equals the order after reload", + "evidence": "order before/after reload" + }, + { + "clause": "favorites star/unstar persists across reload", + "oracle": "dom", + "verify": "the starred item is present in favorites before and after reload; gone after unstar", + "evidence": "pre/post-reload favorites list" + }, + { + "clause": "the Recents rail lists recently-visited entities (top 5) and survives reload (RecentItemsProvider)", + "oracle": "dom", + "verify": "the visited entities appear in the Recent section after reload", + "evidence": "visit sequence + post-reload recents" + }, + { + "clause": "header record-trail breadcrumbs render on a record and each crumb navigates back to its level; the trail rebuilds after reload", + "oracle": "screenshot", + "verify": "breadcrumb screenshot + a crumb navigation that routes back + the trail present after reload", + "evidence": "breadcrumb screenshot + the crumb navigation" + } + ], + "negative": [ + "a collapsed sidebar whose icon items no longer navigate (dead icon mode) is a FAIL", + "a pin / favorite / reorder / recent that does NOT survive reload is a FAIL of its persistence contract", + "a breadcrumb crumb that is inert (does not navigate) is a FAIL", + "transitional emptiness right after navigation (empty nav/recents) must be ruled out by the screenshot-first protocol before any persistence FAIL is recorded" + ], + "traps": ["hydration-race", "shared-browser-tab"], + "source": [ + "objectui packages/app-shell/src/layout/UnifiedSidebar.tsx (useNavOrder drag-reorder localStorage; Recent section; applyPins)", + "objectui packages/app-shell/src/hooks/useNavPins.ts (togglePin → nav:<navId> favorite, MAX_PINS 20, UserDataAdapter-backed, legacy objectui-nav-pins migration)", + "objectui packages/app-shell/src/hooks/useFavorites.ts + context/FavoritesProvider.tsx (favorites state)", + "objectui packages/app-shell/src/context/RecentItemsProvider.tsx + hooks/useTrackRouteAsRecent.ts (recents — objectui-recent-items localStorage + UserDataAdapter hydrate)", + "objectui packages/app-shell/src/layout/AppHeader.tsx (breadcrumb record-trail)", + "objectui packages/components/src/ui/sidebar.tsx (SIDEBAR_COOKIE_NAME sidebar_state — collapse persistence)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "initial — sidebar collapse/icon-mode, nav pin+reorder, favorites, recents rail, header breadcrumbs, each with its real persistence layer (cookie/localStorage/UserDataAdapter)", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "platform-core.app-management-toggle", + "title": "App management: the launcher/App Switcher filters by active+hidden and isDefault drives post-login landing — but the AppManagementPage toggle is a client-only stub today", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": [ + "seeded admin (admin@objectos.ai / admin123)", + "a plain member (fresh runtime sign-up — member_default)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "a writable package to author an app overlay into (POST /api/v1/packages) so the active/isDefault EFFECT can be exercised at the metadata layer — the seeded showcase/platform apps are read-only code packages (ADR-0070)" + ], + "knownGaps": [ + "AppManagementPage's Enable/Disable/Set-default/Delete/Bulk handlers are CLIENT-ONLY STUBS today (objectui apps/console/src/pages/system/AppManagementPage.tsx — every handler is a toast.success() + refresh() carrying a 'TODO: Replace with real API call when backend supports app management' marker; no request is issued and no metadata changes). So the page's toggle CANNOT prove the effect — the launcher/landing behavior is exercised at the metadata layer instead, and the stub is recorded as an expected-fail probe (RUNNER rule 3: a stub that reports success is the finding)" + ] + }, + "steps": [ + "establish the launcher filter is real: the App Switcher (objectui AppSwitcher.tsx) lists only apps with active !== false && hidden !== true. Confirm the Account app (hidden:true — account.app.ts) is ABSENT from the switcher while reachable via the avatar dropdown; Setup/Studio/showcase present", + "prove DISABLE-effect at the metadata layer (the UI toggle is a stub): author an app overlay with active:false into a writable package (POST /api/v1/packages + PUT /api/v1/meta/app/<name> active:false, publish) → confirm it leaves the App Switcher/launcher for end users (absent); flip active:true → it returns", + "prove DEFAULT→landing: the isDefault app drives post-login landing — confirm login lands on the current default app, and that changing which app is isDefault (at the metadata layer) changes the post-login landing route", + "member view: as a plain member the App Switcher shows only accessible apps (Setup/Studio absent by App.requiredPermissions); disabled/hidden apps also absent", + "open Setup → Apps → Applications (AppManagementPage) as admin; it lists configured apps with Active/Default badges and per-row controls", + "EXPECTED-FAIL / known-stub probe: click Disable on a seeded app in AppManagementPage → a success toast appears BUT no request is issued (empty network) and GET /api/v1/meta/app shows the app still active on reload. Record the toast-without-persistence; do NOT credit the app as disabled off the toast", + "same probe for Set-default and Delete: the toast fires, the metadata is unchanged on reload" + ], + "acceptance": [ + { + "clause": "the App Switcher lists only active, non-hidden apps: hidden apps (Account) and inactive apps are absent; active apps present (AppSwitcher.tsx activeApps = active !== false && hidden !== true)", + "oracle": "dom", + "verify": "after a screenshot confirms the switcher rendered, diff its entries against the served /meta/app list — Account (hidden) absent, active apps present", + "evidence": "switcher DOM vs the served app list" + }, + { + "clause": "disabling an app removes it from the launcher for end users — proven by setting active:false at the metadata layer (writable-package overlay), then confirming absence; re-enable returns it", + "oracle": "screenshot", + "verify": "before/after App Switcher screenshots bracketing the active:false and active:true meta writes", + "evidence": "before/after switcher screenshots + the meta writes" + }, + { + "clause": "the default app drives post-login landing: login lands on the isDefault app, and changing the default changes the landing route", + "oracle": "screenshot", + "verify": "post-login screenshots for two different isDefault choices land on different apps", + "evidence": "the two post-login screenshots" + }, + { + "clause": "a member sees only accessible apps in the switcher (Setup/Studio absent by requiredPermissions; disabled/hidden absent)", + "oracle": "dom", + "verify": "the member's switcher DOM excludes Setup/Studio and any disabled/hidden app", + "evidence": "the member switcher DOM" + }, + { + "clause": "AppManagementPage renders the Applications list with Active/Default badges and per-row controls", + "oracle": "screenshot", + "verify": "the page renders the app cards with the Active/Default/Inactive badges", + "evidence": "the page screenshot" + }, + { + "clause": "EXPECTED-FAIL / known-stub probe — AppManagementPage's Disable/Set-default/Delete are client-only stubs: clicking Disable issues NO request (empty network) and GET /api/v1/meta/app is UNCHANGED on reload, while a success toast is shown. A run records the stub and MUST NOT tick 'disabled for end users' off the toast", + "oracle": "network", + "verify": "the network trace shows no PATCH/DELETE on the click; /meta/app unchanged on reload; the toast fired (AppManagementPage TODO handlers)", + "evidence": "the absent request + the unchanged meta + the toast" + } + ], + "negative": [ + "ticking 'app disabled' off the AppManagementPage success toast is a FALSE PASS — the handler is a stub (no backend), the effect must be proven at the metadata/launcher layer", + "the App Switcher showing a hidden (Account) or inactive app to end users is a FAIL of the launcher filter", + "post-login landing that ignores isDefault is a FAIL" + ], + "traps": ["wrong-persona", "stale-console-bundle", "hydration-race"], + "source": [ + "objectui apps/console/src/pages/system/AppManagementPage.tsx (the stubbed Enable/Disable/Set-default/Delete/Bulk handlers — TODO 'when backend supports app management')", + "objectui packages/app-shell/src/layout/AppSwitcher.tsx (activeApps = active !== false && hidden !== true)", + "packages/platform-objects/src/apps/account.app.ts (hidden:true example)", + "packages/runtime/src/route-ledger.ts (PUT /meta/app/:name, POST /packages/:id/publish — the metadata layer that actually changes app active/default)", + "ADR-0048 (app package routing / launcher)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "initial — launcher active+hidden filter and isDefault→landing proven at the metadata layer, with the AppManagementPage toggle recorded as a client-only stub (expected-fail probe) rather than faked", "ref": "claude/platform-test-checklist-ocwugl" } + ] } ] } diff --git a/docs/qa/platform-checklist/areas/records-forms.json b/docs/qa/platform-checklist/areas/records-forms.json index 740d91b02d..f51f2b04d7 100644 --- a/docs/qa/platform-checklist/areas/records-forms.json +++ b/docs/qa/platform-checklist/areas/records-forms.json @@ -8,10 +8,12 @@ "title": "Create → read → update → delete a record through the console UI", "since": "v15", "status": "active", - "revision": 2, + "revision": 3, "priority": "P0", "surface": "browser", - "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], "fixtures": { "app": "showcase", "requires": [ @@ -25,6 +27,8 @@ "re-read server-side: GET /api/v1/data/showcase_account?$filter=[[\"name\",\"=\",\"os-qa-<runid>\"]] — field-by-field vs the submitted payload", "open the record detail; edit exactly one field (annual_revenue); Save; capture the PATCH /api/v1/data/showcase_account/<id>; re-read the full row via GET /api/v1/data/showcase_account/<id>", "open the record's History tab (screenshot first, then read entries)", + "clone the record: POST /api/v1/data/showcase_account/<id>/clone (optionally with {overrides:{name:'os-qa-<runid>-clone'}}); capture the 201 result (new id ≠ source, sourceId, record); re-read the clone via GET /api/v1/data/showcase_account/<new id>", + "RLS probe on clone: as a persona WITHOUT read on the source row (a permission-zoo persona, or a non-owner under owner-RLS), POST the same clone route and capture the refusal — expect 404 RECORD_NOT_FOUND, never a silent duplicate (record blocked(fixture) if no non-reading persona is bound on this boot)", "delete via the row/detail action; confirm the dialog; re-read via the filtered GET", "reload the list view and confirm final state" ], @@ -64,23 +68,58 @@ "oracle": "api", "verify": "filtered GET returns the CJK name unmangled; $search finds it (the seed's 华宁科技 proves the pattern — ADR-0061)", "evidence": "re-read JSON + search response" + }, + { + "clause": "clone (POST /data/:object/:id/clone, gated by enable.clone default-on) returns 201 with a NEW id and the source's field VALUES copied, but engine-owned columns (id, audit, autonumber, formula, summary) and readonly columns (e.g. approval_status) RE-DERIVED not carried, and the clone is owned by the CLONER — not the source's owner", + "oracle": "api", + "verify": "the 201 result carries {id (new ≠ sourceId), sourceId, record}; field-by-field diff shows business values copied and system/readonly columns re-derived (#3043 CLONE_STRIP_FIELDS + stripReadonlyForInsert); owner_id resolves to the signed-in cloner (the clone is a create in the caller's context — packages/metadata-protocol/src/protocol.ts cloneData)", + "evidence": "clone response + source-vs-clone field diff + owner_id read" + }, + { + "clause": "clone is RLS-gated: the source is fetched in the caller's context (engine.findOne with context), so cloning a record the caller cannot SEE is refused with 404 RECORD_NOT_FOUND — never a silent duplicate of an invisible row", + "oracle": "api", + "verify": "as a persona without read on the source row, POST the clone route → 404 RECORD_NOT_FOUND and no new row lands (recordNotFoundError from the null findOne); an enable.clone:false object refuses with 403 CLONE_DISABLED", + "evidence": "the RLS refusal + a post-attempt count showing no new row" } ], "negative": [ "save with required status empty → the form blocks with a named field error AND no row is created (filtered API count stays 0) — a silent success is a FAIL", + "a clone that carries over the source's id / audit columns / an autonumber / a readonly approval_status instead of re-deriving them is a FAIL (#3043 — a clone must not mint an already-approved record); a clone of an RLS-invisible source that SUCCEEDS (200/201 with a new row) is a FAIL — the findOne runs in the caller's context precisely to refuse it", "a direct API POST missing required status → 400 VALIDATION_FAILED with fields[] carrying code 'required' (server enforces, not just the form — packages/objectql/src/validation/record-validator.ts)", "a create with tax_id violating the tax_id_format rule → named validation error, never a silently-stored bad value" ], - "traps": ["hydration-race", "automation-input"], + "traps": [ + "hydration-race", + "automation-input" + ], "source": [ "dogfood-verification skill §3", "examples/app-showcase/src/data/objects/account.object.ts (requiredness + format/conditional validations)", "packages/runtime/src/route-ledger.ts (/data CRUD routes)", - "objectui: e2e/live/record-history-display.spec.ts" + "packages/rest/src/rest-server.ts (POST /data/:object/:id/clone → registerDataActionEndpoints) + packages/metadata-protocol/src/protocol.ts (cloneData: enable.clone gate, findOne-in-caller-context, CLONE_STRIP_FIELDS, stripReadonlyForInsert)", + "packages/rest/src/rest-route-ledger.ts:122 (POST /api/v1/data/:object/:id/clone, client data.clone)", + "objectui: e2e/live/record-history-display.spec.ts", + "cross-ref: the inline-edit atomic two-surface behavior (ONE Save bar / ONE PATCH carrying exactly the changed keys + ifMatch) is folded into records-forms.concurrent-edit-conflict, not here" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — standing P0 smoke", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — standing P0 smoke", + "ref": "#3358" + }, + { + "revision": 2, + "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-08-08", + "change": "added the /data/:object/:id/clone clauses (new id + copied field values, cloner ownership, engine/readonly column re-derivation, RLS-invisible source refused RECORD_NOT_FOUND, enable.clone gate); inline-edit two-surface behavior placed in concurrent-edit-conflict with a cross-ref here", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -91,7 +130,9 @@ "revision": 1, "priority": "P1", "surface": "mixed", - "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], "fixtures": { "app": "showcase", "requires": [ @@ -104,18 +145,54 @@ ] }, "variants": [ - "text", "textarea", "email", "url", "phone", "password", "secret", - "markdown", "html", "richtext", - "number", "currency", "percent", - "date", "datetime", "time", - "boolean", "toggle", - "select", "multiselect", "radio", "checkboxes", - "lookup", "master_detail", "tree", "user", - "image", "file", "avatar", "video", "audio", - "formula", "summary", "autonumber", - "composite", "repeater", "record", - "location", "address", "code", "json", "color", "rating", "slider", - "signature", "qrcode", "progress", "tags", + "text", + "textarea", + "email", + "url", + "phone", + "password", + "secret", + "markdown", + "html", + "richtext", + "number", + "currency", + "percent", + "date", + "datetime", + "time", + "boolean", + "toggle", + "select", + "multiselect", + "radio", + "checkboxes", + "lookup", + "master_detail", + "tree", + "user", + "image", + "file", + "avatar", + "video", + "audio", + "formula", + "summary", + "autonumber", + "composite", + "repeater", + "record", + "location", + "address", + "code", + "json", + "color", + "rating", + "slider", + "signature", + "qrcode", + "progress", + "tags", "vector" ], "steps": [ @@ -177,8 +254,15 @@ "a lookup/master_detail/tree write pointing at a nonexistent row accepted with 200 is a FAIL (#4441 closed exactly this hole)", "any GET returning f_secret/f_password plaintext is a FAIL regardless of what the form shows" ], - "traps": ["hydration-race", "automation-input", "stale-console-bundle"], - "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/field-zoo-roundtrip.dogfood.test.ts (+ field-zoo.matrix.ts vectors, field-zoo-value-shape.test.ts contract)" }, + "traps": [ + "hydration-race", + "automation-input", + "stale-console-bundle" + ], + "automated": { + "kind": "api", + "ref": "packages/qa/dogfood/test/field-zoo-roundtrip.dogfood.test.ts (+ field-zoo.matrix.ts vectors, field-zoo-value-shape.test.ts contract)" + }, "source": [ "packages/spec/src/data/field.zod.ts (FieldType enum — 49 members, listed exhaustively in variants)", "examples/app-showcase/src/data/objects/field-zoo.object.ts", @@ -187,7 +271,12 @@ "objectui: e2e/live/summary-rollup.spec.ts" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — deep-test variant matrix over FieldTypeSchema, pinned to the dogfood HTTP round-trip suite", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — deep-test variant matrix over FieldTypeSchema, pinned to the dogfood HTTP round-trip suite", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -198,7 +287,9 @@ "revision": 1, "priority": "P1", "surface": "browser", - "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], "fixtures": { "app": "showcase", "requires": [ @@ -299,8 +390,15 @@ "userFilters leaking onto an OBJECT list view is a FAIL — ADR-0053 suppresses them there by design (filter elements belong to interface pages; objectui warns since #2220)", "an export that returns the unfiltered table for a filtered view is a FAIL even though a file downloaded" ], - "traps": ["hydration-race", "automation-input", "stale-console-bundle"], - "automated": { "kind": "e2e", "ref": "objectui: e2e/live/saved-view-filter.spec.ts, e2e/live/user-filters.spec.ts, e2e/live/inline-edit-polish-2572.spec.ts; packages/qa/dogfood/test/showcase-search.dogfood.test.ts" }, + "traps": [ + "hydration-race", + "automation-input", + "stale-console-bundle" + ], + "automated": { + "kind": "e2e", + "ref": "objectui: e2e/live/saved-view-filter.spec.ts, e2e/live/user-filters.spec.ts, e2e/live/inline-edit-polish-2572.spec.ts; packages/qa/dogfood/test/showcase-search.dogfood.test.ts" + }, "source": [ "examples/app-showcase/src/ui/views/task.view.ts (saved views, sort string form, inlineEdit, exportOptions, bulk/row actions, allowedVisualizations)", "examples/app-showcase/src/ui/views/field-zoo.view.ts (conditionalFormatting, gated bulk actions)", @@ -309,7 +407,12 @@ "examples/app-showcase/src/data/seed/index.ts (10 tasks / 2 in_progress; CJK rows)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — deep-test capability matrix for list surfaces, pinned to the objectui live specs where they exist", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — deep-test capability matrix for list surfaces, pinned to the objectui live specs where they exist", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -320,7 +423,9 @@ "revision": 1, "priority": "P1", "surface": "browser", - "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], "fixtures": { "app": "showcase", "requires": [ @@ -332,7 +437,17 @@ "task 'cover' (gallery coverField) is declared but deliberately unseeded (#4891 / ADR-0104 — a managed sys_file cannot honestly be seeded); the gallery renders coverless cards by design — do not fail the gallery variant on missing covers, upload one to prove the cover path" ] }, - "variants": ["grid", "kanban", "gallery", "calendar", "timeline", "gantt", "map", "chart", "tree"], + "variants": [ + "grid", + "kanban", + "gallery", + "calendar", + "timeline", + "gantt", + "map", + "chart", + "tree" + ], "steps": [ "open /_console/apps/com.example.showcase/showcase_task and walk its named views: Grid, Board (Kanban), Cards (Gallery), Calendar, Activity Timeline, Schedule (Gantt), Work Locations (Map), Hours by Status (Chart)", "on each: wait for render, screenshot FIRST, then read the DOM for the visualization's own structure (kanban columns, calendar cells, gantt bars, map markers, chart SVG)", @@ -384,8 +499,15 @@ "a visualization that renders as a plain grid (fallback) while its type claims kanban/calendar/gantt/etc. is a FAIL for that variant — 'it rendered something' is not the oracle", "a chart that renders from a single datapoint proves little — note the weakness in evidence rather than ticking silently (single-datapoint trap)" ], - "traps": ["hydration-race", "single-datapoint", "wrong-panel"], - "automated": { "kind": "e2e", "ref": "examples/app-showcase/e2e/showcase-smoke.spec.ts (page-level render for the view surfaces)" }, + "traps": [ + "hydration-race", + "single-datapoint", + "wrong-panel" + ], + "automated": { + "kind": "e2e", + "ref": "examples/app-showcase/e2e/showcase-smoke.spec.ts (page-level render for the view surfaces)" + }, "source": [ "packages/spec/src/ui/view.zod.ts (ListViewSchema type enum: grid|kanban|gallery|calendar|timeline|gantt|map|chart|tree)", "examples/app-showcase/src/coverage.ts (LIST_VIEW_TYPES — the 8 the task object demonstrates)", @@ -394,7 +516,12 @@ "examples/app-showcase/src/data/seed/index.ts (view-feeding seed shape)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — deep-test view-type gallery derived from the spec's own enum, 8 types on task + tree on business-unit", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — deep-test view-type gallery derived from the spec's own enum, 8 types on task + tree on business-unit", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -405,7 +532,9 @@ "revision": 1, "priority": "P1", "surface": "browser", - "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], "fixtures": { "app": "showcase", "requires": [ @@ -415,7 +544,14 @@ "the spec's 6th form type, 'modal', is not authored anywhere in the showcase (coverage.ts FORM_VIEW_TYPES deliberately lists 5) — record the modal variant blocked(fixture) rather than ticking or silently skipping it" ] }, - "variants": ["simple", "tabbed", "wizard", "split", "drawer", "modal"], + "variants": [ + "simple", + "tabbed", + "wizard", + "split", + "drawer", + "modal" + ], "steps": [ "open a task record and its 'edit' (simple) form view; screenshot; verify the single 2-column section with the declared field order", "open the tabbed form view; verify the three tabs (Overview / Schedule / Details) and that switching tabs preserves entered values", @@ -461,14 +597,22 @@ "a wizard that lets Next past a required title with no named field error — or that silently writes per step — is a FAIL", "counting the modal variant as passed because the other five rendered is a FAIL: it must be recorded blocked(fixture) with this item cited" ], - "traps": ["hydration-race", "automation-input"], + "traps": [ + "hydration-race", + "automation-input" + ], "source": [ "packages/spec/src/ui/view.zod.ts (FormViewSchema type enum: simple|tabbed|wizard|split|drawer|modal)", "examples/app-showcase/src/coverage.ts (FORM_VIEW_TYPES — the 5 authored)", "examples/app-showcase/src/ui/views/task.view.ts (formViews incl. visibleWhen on notes)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — deep-test form-view gallery derived from the spec enum; modal recorded as a standing fixture gap", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — deep-test form-view gallery derived from the spec enum; modal recorded as a standing fixture gap", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -479,7 +623,9 @@ "revision": 1, "priority": "P1", "surface": "mixed", - "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], "fixtures": { "app": "showcase", "requires": [ @@ -540,15 +686,27 @@ "a PATCH to tax_rate on a paid invoice whose new value PERSISTS is a FAIL — silent acceptance of a locked field is the defect this rule exists to stop", "a Delete affordance on a paid invoice row is a FAIL even if clicking it would error later" ], - "traps": ["hydration-race", "automation-input", "stale-console-bundle"], - "automated": { "kind": "e2e", "ref": "objectui: e2e/live/field-conditional-rules.spec.ts, e2e/live/required-when-submit.spec.ts, e2e/live/list-row-action-cel.spec.ts" }, + "traps": [ + "hydration-race", + "automation-input", + "stale-console-bundle" + ], + "automated": { + "kind": "e2e", + "ref": "objectui: e2e/live/field-conditional-rules.spec.ts, e2e/live/required-when-submit.spec.ts, e2e/live/list-row-action-cel.spec.ts" + }, "source": [ "examples/app-showcase/src/data/objects/invoice.object.ts (the B2 rules + userActions gating, with server-semantics comments)", "packages/spec/src/data/field.zod.ts (requiredWhen/readonlyWhen/visibleWhen authoring surface)", "packages/objectql/src/validation/rule-validator.ts (enforcement site)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — deep-test both-ends contract for header-level conditional rules on the seeded invoices", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — deep-test both-ends contract for header-level conditional rules on the seeded invoices", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -559,7 +717,9 @@ "revision": 1, "priority": "P1", "surface": "browser", - "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], "fixtures": { "app": "showcase", "requires": [ @@ -612,14 +772,25 @@ "a batch save that succeeds with quantity 120 and empty description is a FAIL on both the client clause and the server clause", "cells still editable on a paid invoice (or still locked after reverting) is a FAIL — the parent scope must re-evaluate live" ], - "traps": ["hydration-race", "automation-input"], - "automated": { "kind": "e2e", "ref": "objectui: e2e/live/grid-conditional-rules.spec.ts, e2e/live/grid-parent-rules.spec.ts, e2e/live/grid-file-upload.spec.ts" }, + "traps": [ + "hydration-race", + "automation-input" + ], + "automated": { + "kind": "e2e", + "ref": "objectui: e2e/live/grid-conditional-rules.spec.ts, e2e/live/grid-parent-rules.spec.ts, e2e/live/grid-file-upload.spec.ts" + }, "source": [ "examples/app-showcase/src/data/objects/invoice.object.ts (row/parent-scoped rules on the line object; ADR-0036 / #1581)", "objectui: e2e/live/grid-conditional-rules.spec.ts, grid-parent-rules.spec.ts" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — deep-test grid conditional rules split from the header item (different scopes, different pins)", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — deep-test grid conditional rules split from the header item (different scopes, different pins)", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -630,7 +801,10 @@ "revision": 1, "priority": "P1", "surface": "mixed", - "personas": ["seeded admin (admin@objectos.ai / admin123)", "non-admin user (sign one up — sys_user rows cannot be seeded)"], + "personas": [ + "seeded admin (admin@objectos.ai / admin123)", + "non-admin user (sign one up — sys_user rows cannot be seeded)" + ], "fixtures": { "app": "showcase", "requires": [ @@ -683,15 +857,27 @@ "an out-of-set province accepted with 200 is a FAIL — client hiding is UX, the objectql rule-validator is the boundary", "a non-admin's direct tier='restricted' POST accepted is a FAIL even though their picker hid the option (UI absence alone is a client courtesy — RUNNER rule 4)" ], - "traps": ["hydration-race", "automation-input", "wrong-persona"], - "automated": { "kind": "e2e", "ref": "objectui: e2e/live/cascading-options.spec.ts" }, + "traps": [ + "hydration-race", + "automation-input", + "wrong-persona" + ], + "automated": { + "kind": "e2e", + "ref": "objectui: e2e/live/cascading-options.spec.ts" + }, "source": [ "examples/app-showcase/src/data/objects/cascading-select.object.ts (the B3 / #1583 fixture, with both-sides contract in its header comment)", "examples/app-showcase/src/data/objects/invoice.object.ts (contact dependsOn account)", "examples/app-showcase/src/data/seed/index.ts (contact spread per account)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — deep-test dynamic options: cascade, role gate, dependent lookup, server-side rejection", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — deep-test dynamic options: cascade, role gate, dependent lookup, server-side rejection", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -702,7 +888,9 @@ "revision": 2, "priority": "P1", "surface": "browser", - "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], "fixtures": { "app": "showcase", "requires": [ @@ -761,32 +949,56 @@ "the invalid-child case must not partially commit: ANY surviving master or sibling row after the failed save is a FAIL — check by API read, not by the grid", "a save that issues one write per row (N requests) is a FAIL of the atomicity clause even when all rows land" ], - "traps": ["automation-input", "hydration-race"], - "automated": { "kind": "e2e", "ref": "objectui: e2e/live/master-detail.spec.ts, e2e/live/summary-rollup.spec.ts, e2e/live/form-view-subforms.spec.ts" }, + "traps": [ + "automation-input", + "hydration-race" + ], + "automated": { + "kind": "e2e", + "ref": "objectui: e2e/live/master-detail.spec.ts, e2e/live/summary-rollup.spec.ts, e2e/live/form-view-subforms.spec.ts" + }, "source": [ "#3358 §4", "examples/app-showcase/src/data/objects/invoice.object.ts (inlineEdit grid, amount expression, total summary)", "examples/app-showcase/src/data/seed/index.ts (product catalog rows)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from #3358", + "ref": "#3358" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { "id": "records-forms.related-list-server-pagination", - "title": "Related lists derive from the relationship and page on the server — never fetch every child row", + "title": "Related lists derive from the relationship and page on the server — never fetch every child row — and are read-gated on both ends", "since": "v16", "status": "active", - "revision": 2, + "revision": 3, "priority": "P1", "surface": "browser", - "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "personas": [ + "seeded admin (admin@objectos.ai / admin123)", + "showcase_manager — reads BOTH showcase_account AND showcase_contact (readScope org) — the CHILD-read-entitled persona", + "showcase_contributor — reads showcase_account but NOT showcase_contact — the CHILD-read-DENIED persona" + ], "fixtures": { "app": "showcase", "requires": [ "Northwind account with 26 seeded contacts (2 named + 24 'Prospect NN' rows authored precisely so the related list exceeds a page — examples/app-showcase/src/data/seed/index.ts, objectui#2711)", - "showcase_invoice.account declares relatedList: 'primary' with relatedListTitle 'Invoices' and relatedListColumns [name, status, total, issued_on] (examples/app-showcase/src/data/objects/invoice.object.ts)" + "showcase_invoice.account declares relatedList: 'primary' with relatedListTitle 'Invoices' and relatedListColumns [name, status, total, issued_on] (examples/app-showcase/src/data/objects/invoice.object.ts)", + "the permission-zoo sets examples/app-showcase/src/security/permission-sets.ts: showcase_manager grants showcase_contact read (line 124), showcase_contributor omits it (lines 36-45) — the both-sides read-gate probe binds a user to each" + ], + "knownGaps": [ + "the child-read-gate clause needs two signed-up users bound to showcase_manager and showcase_contributor — sys_user rows come from sign-up, not seeds — so on a single-user stock boot record that clause blocked(fixture); the paging clauses run on the seeded admin alone" ] }, "steps": [ @@ -796,7 +1008,9 @@ "click next-page; capture the page-2 request; note the pager text", "diff page-1 and page-2 row ids for overlap", "open the Invoices related tab; verify its title and columns match the declaration on the lookup field (name, status, total, issued_on)", - "capture every data request issued by the detail page and check each child query for a $top bound" + "capture every data request issued by the detail page and check each child query for a $top bound", + "read-gate both-sides: sign in as showcase_contributor (reads showcase_account, NOT showcase_contact); open the Northwind account detail; screenshot and confirm the Contacts related section/tab is ABSENT; then forge GET /api/v1/data/showcase_contact?$filter=[[\"account\",\"=\",\"<northwind id>\"]] as that persona and capture the 403", + "repeat as showcase_manager (has showcase_contact read): confirm the Contacts section renders AND the same child query answers 200 with rows" ], "acceptance": [ { @@ -828,22 +1042,52 @@ "oracle": "api", "verify": "GET /api/v1/data/showcase_contact/<clicked id> matches the row navigated to", "evidence": "navigation screenshot + the read" + }, + { + "clause": "related lists are READ-gated on BOTH ends: a persona WITHOUT read on the child object (showcase_contributor lacks showcase_contact read) sees NO Contacts section on the account detail (UI courtesy — deriveRelatedLists drops children the user cannot read, objectui#2359) AND a direct child query is refused server-side (403); a persona WITH child read (showcase_manager) sees the section AND the query 200s", + "oracle": "api", + "verify": "as showcase_contributor: screenshot confirms the Contacts tab is absent, and the forged GET /api/v1/data/showcase_contact?$filter=[[\"account\",\"=\",\"<northwind id>\"]] returns 403; as showcase_manager: the tab renders and the identical query returns 200 with rows — the server is the authority (ADR-0057 D10, RUNNER rule 4), the UI drop is courtesy", + "evidence": "both personas' detail screenshots + the 403 and the 200 child queries" } ], "negative": [ "any child-list request WITHOUT a $top bound is a FAIL even when the rendered page looks right — the unpaged fetch is the defect", - "a pager total that disagrees with a direct filtered API count (GET /api/v1/data/showcase_contact?$filter=[[\"account\",\"=\",\"<northwind id>\"]]) is a FAIL" + "a pager total that disagrees with a direct filtered API count (GET /api/v1/data/showcase_contact?$filter=[[\"account\",\"=\",\"<northwind id>\"]]) is a FAIL", + "a Contacts section rendering for showcase_contributor (an empty grid + a New button that 403s on save) is the objectui#2359 regression shape — FAIL; equally, the child query returning rows to a persona the object read gate denies is a server-side FAIL (UI absence alone never proves the server refuses)" + ], + "traps": [ + "hydration-race" ], - "traps": ["hydration-race"], - "automated": { "kind": "e2e", "ref": "objectui: e2e/live/detail-related-list.spec.ts" }, + "automated": { + "kind": "e2e", + "ref": "objectui: e2e/live/detail-related-list.spec.ts" + }, "source": [ "#3358 §4 (evidence run captured exactly this trace)", "examples/app-showcase/src/data/seed/index.ts (the 26-contact Northwind fixture, authored for objectui#2711)", - "examples/app-showcase/src/data/objects/invoice.object.ts (relatedList declaration)" + "examples/app-showcase/src/data/objects/invoice.object.ts (relatedList declaration)", + "objectui: packages/app-shell/src/views/RecordDetailView.tsx (deriveRelatedLists canRead filter — object-level READ gate, objectui#2359)", + "examples/app-showcase/src/security/permission-sets.ts (showcase_manager child read vs showcase_contributor's omission); PENDING-GAPS §E3 / objectui#2565" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial import from the #3358 evidence run", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from the #3358 evidence run", + "ref": "#3358" + }, + { + "revision": 2, + "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-08-08", + "change": "added the §E3 read-gating both-sides clause (child-object read gate: related section absent in the UI AND child query 403s server-side) with the permission-zoo personas (manager reads child, contributor does not); objectui#2359/#2565", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -854,7 +1098,9 @@ "revision": 2, "priority": "P1", "surface": "mixed", - "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], "fixtures": { "app": "showcase", "requires": [ @@ -912,8 +1158,14 @@ "maxSize is enforced in JS, not as a DOM attribute — do NOT claim it from attribute absence; it needs an oversized-upload attempt (covered by records-forms.upload-guard-blocks-confirm)", "a malformed param bag that reaches the action body (echo shows the bad key accepted) is a FAIL — the dispatch gate, not the widget, is the boundary" ], - "traps": ["hydration-race", "automation-input"], - "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/action-params-contract.dogfood.test.ts; objectui: e2e/live/action-modal.spec.ts" }, + "traps": [ + "hydration-race", + "automation-input" + ], + "automated": { + "kind": "api", + "ref": "packages/qa/dogfood/test/action-params-contract.dogfood.test.ts; objectui: e2e/live/action-modal.spec.ts" + }, "source": [ "#3358 §4 (evidence table)", "#3393", @@ -921,8 +1173,18 @@ "ADR-0059 (param-dialog widgets)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358, keeping its maxSize caveat as a negative-side note", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from #3358, keeping its maxSize caveat as a negative-side note", + "ref": "#3358" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -933,7 +1195,9 @@ "revision": 2, "priority": "P2", "surface": "browser", - "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], "fixtures": { "app": "showcase", "requires": [ @@ -978,15 +1242,28 @@ "a confirm click mid-upload that ISSUES the dispatch is a FAIL even if the server would cope", "an oversized file that starts uploading (any upload request observed) is a FAIL — the guard must reject before the wire" ], - "traps": ["automation-input", "hydration-race"], + "traps": [ + "automation-input", + "hydration-race" + ], "source": [ "#3358 §4 ('leaving it unticked on the strength of a label')", "ADR-0059", "examples/app-showcase/src/ui/actions/index.ts (maxSize declarations)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — codifies the #3358 refusal to tick from a label into the oracle itself", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — codifies the #3358 refusal to tick from a label into the oracle itself", + "ref": "#3358" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -997,7 +1274,9 @@ "revision": 2, "priority": "P2", "surface": "browser", - "personas": ["seeded admin (admin@objectos.ai / admin123)"], + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], "fixtures": { "app": "showcase", "requires": [ @@ -1041,13 +1320,1354 @@ "a GBK file whose preview renders mojibake while the tests pass means the WIRED wizard regressed against the pinned parser — a FAIL, file against the wizard wiring", "an import that reports success while the filtered API read finds no rows is a FAIL (silent drop)" ], - "automated": { "kind": "unit", "ref": "objectui: packages/plugin-grid/src/importParsers.test.ts (+ importMissingRequiredHint, importLegacyReferenceGuard)" }, - "traps": ["automation-input", "stale-console-bundle"], - "source": ["#3358 §4 (ticked on test evidence — the native file picker hand-off is deliberately not automated)"], + "automated": { + "kind": "unit", + "ref": "objectui: packages/plugin-grid/src/importParsers.test.ts (+ importMissingRequiredHint, importLegacyReferenceGuard)" + }, + "traps": [ + "automation-input", + "stale-console-bundle" + ], + "source": [ + "#3358 §4 (ticked on test evidence — the native file picker hand-off is deliberately not automated)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from #3358's test-evidence resolution", + "ref": "#3358" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.action-location-matrix", + "title": "Action buttons surface at exactly their declared locations — list toolbar, list row, record header/more/related/section, global nav — and each dispatches for real", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": [ + "admin" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the per-location action fleet on showcase_task (examples/app-showcase/src/ui/actions/index.ts): showcase_bulk_reassign (list_item+list_toolbar, flow), showcase_quick_view (list_item, modal), showcase_mark_done (list_item+record_header+record_section, script, visible '!record.done'), showcase_log_time (record_header+record_related+record_section, form), showcase_open_docs (record_more, url), showcase_recalc_selection (record_more, api — deliberately kept OFF the toolbar, objectui#3142), showcase_new_task (global_nav, modal)", + "seeded tasks in both done and not-done states for the CEL visibility both-sides check" + ] + }, + "variants": [ + "list_toolbar — showcase_bulk_reassign on the task list header", + "list_item — showcase_quick_view / showcase_mark_done in the row menu", + "record_header — showcase_mark_done / showcase_log_time in the detail title bar", + "record_more — showcase_open_docs / showcase_recalc_selection under the ⋯ overflow", + "record_related — showcase_log_time on the related-list section", + "record_section — showcase_mark_done / showcase_log_time in the Task Detail quick-actions bar (record:quick_actions resolves through the location filter)", + "global_nav — showcase_new_task in the command palette / global nav", + "empty-locations semantics probe — a locations-less action lands on EVERY location including the toolbar (objectui action-bar.tsx documented behavior; the reason recalc_selection must declare record_more)" + ], + "steps": [ + "boot the showcase isolated; sign in as admin; open the showcase_task list view", + "for each location variant, navigate to its surface (list header / row menu / a not-done task's detail title bar / its ⋯ menu / a related-list section / the Task Detail quick-actions bar / the global command palette), screenshot AFTER render settles, then read the rendered action buttons from the DOM", + "record for every fixture action WHERE it rendered — building the full placement matrix (rendered locations vs declared locations)", + "dispatch one action per location with a ref-targeted click: bulk_reassign (screen-flow wizard opens), quick_view (modal opens), mark_done (script executes), log_time (form dialog opens on showcase_task.edit), open_docs (url navigation), recalc_selection from the ⋯ menu (api POST), new_task from the palette; capture each network trace", + "verify the state-changing dispatches server-side: mark_done flips the task's done flag (API re-read), recalc_selection's per-record branch recomputes the estimate", + "CEL visibility both sides: locate a done task and a not-done task; read the row menu and record header of each for showcase_mark_done", + "empty-locations probe: in a scratch/writable package author a copy of an api action with NO locations key; reload and record every surface it appears on (including the toolbar), then delete the probe" + ], + "acceptance": [ + { + "clause": "PER-VARIANT: every location renders at least one action declared for it, in the correct UI slot — the full placement matrix (rendered vs declared) has zero missing placements", + "oracle": "dom", + "verify": "after each surface's screenshot confirms render, the DOM read lists the expected action names in that slot; matrix compiled per variant", + "evidence": "per-location screenshots + the placement matrix" + }, + { + "clause": "placement is EXCLUSIVE, not additive: the matrix has zero extra placements — record_more-only actions (open_docs, recalc_selection) never render in record_header or list_toolbar; global_nav-only new_task never renders on rows; the engine location-filters even explicitly-named actions (the record:quick_actions bar note in the fixture source)", + "oracle": "dom", + "verify": "the placement matrix's extra-placement cells are all empty, checked against every captured surface", + "evidence": "the same matrix, extra-placement columns" + }, + { + "clause": "each location's sampled action DISPATCHES for real — flow wizard opens and resumes, modal opens, script executes, form opens the declared edit form view, url navigates, api POSTs — and state-changing ones round-trip server-side (mark_done flips done; recalc updates the estimate)", + "oracle": "network", + "verify": "one captured dispatch per location + API re-reads for the two state changes", + "evidence": "the seven traces + the two re-reads" + }, + { + "clause": "row-level CEL visibility gates per record, both sides: showcase_mark_done ('visible: !record.done') renders on the not-done task's row/header and is ABSENT on the done task's — and the evaluation is fail-closed (a throwing expression hides, never shows)", + "oracle": "dom", + "verify": "side-by-side DOM reads of the two rows and the two record headers", + "evidence": "the four reads + screenshots" + }, + { + "clause": "empty/missing locations means EVERY location — the probe action appears on all surfaced slots including the list toolbar (the objectui#3142 semantics that forces recalc_selection to declare record_more, because a toolbar dispatch has no selection and the endpoint rejects it)", + "oracle": "dom", + "verify": "the locations-less probe's placement list covers all applicable surfaces; recalc_selection itself stays OFF the toolbar", + "evidence": "probe placement list + toolbar DOM read" + } + ], + "negative": [ + "any action rendering at a location it did not declare (and did not inherit via the empty-locations rule) is a FAIL — placement is a contract, not a hint", + "showcase_recalc_selection appearing on the list toolbar is the objectui#3142 regression shape — FAIL even though clicking it would merely error", + "a dispatch that opens the wrong target (e.g. log_time opening a list view instead of the showcase_task.edit form — the #2554 build-gate class) is a FAIL of the dispatch clause, not a cosmetic note" + ], + "traps": [ + "hydration-race", + "automation-input", + "stale-console-bundle" + ], + "automated": { + "kind": "e2e", + "ref": "objectui: e2e/live/list-row-action-cel.spec.ts (row-level CEL visibility) + e2e/live/action-modal.spec.ts (dialog dispatch)" + }, + "source": [ + "packages/spec/src/ui/action.zod.ts:397 (ACTION_LOCATIONS — the canonical 7-value enum, single source of truth)", + "examples/app-showcase/src/ui/actions/index.ts (per-location fixture fleet + the record:quick_actions filter note + the objectui#3142 empty-locations commentary)", + "objectui: packages/.../action-bar.tsx (missing/empty locations → every location)", + "cross-ref: bulk dispatch-count semantics live in records-forms.list-view-capabilities (bulk-actions variant); param dialogs in records-forms.action-param-widgets" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — closes the button-placement gap: list toolbar / list row / bulk bar / detail-page buttons were covered piecemeal but never as the ACTION_LOCATIONS matrix", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.validation-rule-type-matrix", + "title": "All six validation-rule types enforce on the write path with their exact per-type error codes", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": [ + "admin" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the seeded per-type rules: showcase_account account_lifecycle (state_machine), tax_id_format + billing_email_format (format), support_config_shape (json_schema), churn_reason_consistency (conditional); showcase_project end_after_start (cross_field), spent_within_budget (script), project_status_flow (state_machine)" + ] + }, + "variants": [ + "state_machine — create with non-initial status → invalid_initial_state; illegal transition → invalid_transition", + "format — bad tax_id / billing_email → invalid_format", + "json_schema — off-schema support_config → json_schema_violation; non-JSON string → invalid_json", + "cross_field — project end_date < start_date → rule_violation naming end_date", + "script — spent > budget → rule_violation", + "conditional — status 'churned' without churn_reason → the wrapped rule fires; with churn_reason present it does not" + ], + "steps": [ + "boot showcase isolated; sign in as admin", + "for each variant: POST/PATCH the violating payload over /api/v1/data/<object>; capture status + error body", + "for each variant: send the happy-path twin (same shape, satisfying values) and capture success", + "after every rejection, re-read the row set to confirm nothing persisted", + "for state_machine additionally: create with a legal initial state, walk one legal transition, then attempt the illegal one" + ], + "acceptance": [ + { + "clause": "each violating write answers 400 VALIDATION_FAILED with the per-type field code exactly as ledgered: invalid_initial_state / invalid_transition / invalid_format / json_schema_violation / invalid_json / rule_violation — six variants, six distinct proofs, none inferred from a sibling", + "oracle": "api", + "verify": "per-variant response status + fields[].code against the rule-validator dispatch (packages/objectql/src/validation/rule-validator.ts evaluateRule)", + "evidence": "the six response bodies keyed by variant" + }, + { + "clause": "the error targets the declared field (cross_field targets fields[0] per the spec's own comment; format targets the formatted field) — actionable, not a bare object-level failure", + "oracle": "api", + "verify": "fields[].field matches the rule's declared target per variant", + "evidence": "the field targeting in each body" + }, + { + "clause": "no rejected write persists — row counts and byte-identical rows across each rejection", + "oracle": "api", + "verify": "post-rejection re-reads", + "evidence": "the re-reads" + }, + { + "clause": "every happy-path twin lands 2xx — the rules gate violations, they do not block legitimate writes", + "oracle": "api", + "verify": "the six success responses + persisted rows", + "evidence": "the twins" + } + ], + "negative": [ + "an unevaluable CEL expression must fail CLOSED (rule_violation), never fail-open silently accepting the write — the rule-validator's documented posture" + ], + "traps": [ + "wrong-persona" + ], + "source": [ + "packages/spec/src/data/validation.zod.ts (ValidationRuleSchema, 6 discriminated variants)", + "packages/objectql/src/validation/rule-validator.ts (evaluateRule switch + per-type codes)", + "examples/app-showcase/src/data/objects/{account,project,task}.object.ts (the seeded rules)", + "#1475 (declared ≠ enforced history: 9 declared → 6 declared+enforced)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — gap found by the capability sweep: 6 rule types all seeded, none individually asserted anywhere", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.gantt-interactions", + "title": "Gantt is interactive, not a picture: drag persists, locked tasks survive auto-schedule, host veto restores", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "personas": [ + "admin" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the task gantt view (task.view.ts 'Schedule (Gantt)': startDateField/endDateField/titleField/progressField)" + ], + "knownGaps": [ + "the stock view declares NO dependenciesField / lockField / parentField, so auto-schedule + locked-task + subtree-drag surfaces are unreachable on stock fixtures — needs the view extended (objectui carries unit pins for those behaviors: GanttView.autoscheduledlg / summaryedit / deptypes tests)" + ] + }, + "variants": [ + "bar drag → server PATCH (runnable on stock fixture)", + "auto-schedule confirms first: 'Shift N task(s)… (M locked skipped)' and writes NOTHING before confirm (fixture-gated)", + "locked task refuses drag and survives auto-schedule byte-identical (fixture-gated)", + "host onBeforeTaskUpdate veto restores the bar with no write (fixture-gated)" + ], + "steps": [ + "open the task Schedule (Gantt) view; wait for bars to render (screenshot first)", + "drag one task bar to new dates; capture the PATCH and re-read the record over the API", + "reload and confirm the bar re-renders from the persisted dates", + "on an extended fixture (per knownGaps): configure dependencies + a locked row; run toolbar auto-schedule; capture the confirm dialog, cancel once (verify zero writes), run again and apply", + "trigger a veto path (write rejected server-side) and confirm the bar snaps back" + ], + "acceptance": [ + { + "clause": "a bar drag issues the record PATCH and the API re-read shows the new start/end — pixels are not the oracle, the row is", + "oracle": "api", + "verify": "captured PATCH + re-read; reload re-renders from server values", + "evidence": "trace + re-read + post-reload screenshot" + }, + { + "clause": "auto-schedule is confirm-first: cancel writes nothing (row set byte-identical), apply shifts exactly the unlocked affected set and reports skipped locked count", + "oracle": "api", + "verify": "row-set diff after cancel (empty) and after apply (only unlocked tasks moved); dialog text carries N and M", + "evidence": "diffs + dialog screenshot" + }, + { + "clause": "a locked task's dates survive both direct drag attempts and auto-schedule unchanged", + "oracle": "api", + "verify": "before/after reads on the locked row", + "evidence": "the reads" + }, + { + "clause": "a vetoed update leaves no write and restores the visual state", + "oracle": "api", + "verify": "no PATCH lands (or the failed one has no effect) and the re-read is unchanged", + "evidence": "trace + re-read" + } + ], + "negative": [ + "a drag that repaints the bar but lands no PATCH (or a PATCH that 4xxs while the bar keeps the new position) is a FAIL — the #3358 §8 rows exist precisely because gantt can lie visually" + ], + "traps": [ + "hydration-race", + "automation-input" + ], + "automated": { + "kind": "unit", + "ref": "objectui: packages/plugin-gantt/src (GanttView.autoscheduledlg.test.tsx, GanttView.summaryedit.test.tsx, GanttView.deptypes.test.tsx, scheduling.selfextent.test.ts)" + }, + "source": [ + "#3358 §8 (the three never-imported gantt rows)", + "objectui: packages/plugin-gantt/src/GanttView.tsx + scheduling.ts (RescheduleResult.skippedLocked)", + "packages/spec/src/ui/view.zod.ts (GanttConfigSchema)", + "examples/app-showcase/src/ui/views/task.view.ts (stock fixture limits)" + ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358's test-evidence resolution", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — #3358 §8 interaction rows were never imported; render-only coverage existed in view-type-gallery", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.kanban-drag-persistence", + "title": "Kanban card drag across columns persists the group-field change server-side", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "personas": [ + "admin" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the task kanban view (grouped by status) with cards in at least two columns" + ] + }, + "steps": [ + "open the task kanban; screenshot after render", + "drag one card from column A to column B; capture the update request", + "re-read the record over the API; reload the view and locate the card", + "attempt a drag that the server rejects (e.g. an illegal state_machine transition per task_status_flow) and observe the recovery" + ], + "acceptance": [ + { + "clause": "the drop issues the record update carrying the new group-field value and the API re-read confirms it", + "oracle": "api", + "verify": "captured PATCH + re-read shows status = column B's value", + "evidence": "trace + re-read" + }, + { + "clause": "the move survives a reload — the card renders in column B from server state", + "oracle": "screenshot", + "verify": "post-reload screenshot", + "evidence": "screenshot" + }, + { + "clause": "a server-rejected move (illegal FSM transition → 400 invalid_transition) returns the card to its source column with a visible error — not a silently stuck optimistic state", + "oracle": "network", + "verify": "the 400 + the card's post-rejection column + the surfaced error", + "evidence": "trace + screenshot" + } + ], + "negative": [ + "an optimistic move that sticks visually after a failed write is the FAIL this item exists for — cross-checks records-forms.validation-rule-type-matrix's state_machine variant from the UI side" + ], + "traps": [ + "hydration-race", + "automation-input" + ], + "source": [ + "objectui: packages/plugin-kanban/src/KanbanImpl.tsx (onDragEnd)", + "examples/app-showcase/src/data/objects/task.object.ts (task_status_flow state_machine)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — kanban was render-only in view-type-gallery; the drag interaction chain had no coverage", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.form-dirty-guard", + "title": "Dirty forms guard navigation: discard prompts, save proceeds, nothing is lost silently", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "personas": [ + "admin" + ], + "steps": [ + "open a record edit form (modal or drawer); change one field WITHOUT saving", + "attempt to close the dialog / navigate away; capture the guard prompt", + "choose stay/cancel — confirm the edit is still in the form", + "choose discard — confirm the record is unchanged server-side and the form state is dropped", + "repeat the edit and save normally — confirm persistence", + "repeat with NO edits: closing must NOT prompt (clean forms exit freely)" + ], + "acceptance": [ + { + "clause": "a dirty form intercepts close/navigation with the discard guard; a clean form closes without friction — both sides", + "oracle": "screenshot", + "verify": "prompt appears exactly when dirty", + "evidence": "both screenshots" + }, + { + "clause": "discard leaves the server row byte-identical; save persists — the guard's two exits both behave", + "oracle": "api", + "verify": "re-reads after each exit", + "evidence": "the reads" + }, + { + "clause": "the guard's behavior is pinned by the existing unit suite", + "oracle": "test", + "verify": "objectui: pnpm --filter @object-ui/plugin-form exec vitest run src/discardGuard.test.tsx", + "evidence": "test output" + } + ], + "negative": [ + "losing a dirty edit on close with NO prompt is the FAIL; equally, prompting on a pristine form is a paper-cut FAIL of the clean side" + ], + "traps": [ + "automation-input" + ], + "automated": { + "kind": "unit", + "ref": "objectui: packages/plugin-form/src/discardGuard.test.tsx" + }, + "source": [ + "objectui: packages/plugin-form/src/{ModalForm,DrawerForm}.tsx (beforeunload/guard wiring)", + "dogfood-verification skill §4 (the beforeunload escape hatch exists precisely because this guard is real)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — the dirty-state guard existed (with a unit pin) but no checklist item asserted it", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.named-import-mapping", + "title": "A named import mapping maps foreign CSV headers and re-imports idempotently", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "api", + "personas": [ + "admin" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the shipped mapping examples/app-showcase/src/data/mappings/ (showcase_inquiry_feed) targeting showcase_inquiry, with a CSV whose headers are Full Name / Channel (not the field names)" + ] + }, + "steps": [ + "POST /api/v1/data/showcase_inquiry/import with mappingName: 'showcase_inquiry_feed' and a foreign-header CSV", + "read back the created rows over /data", + "POST the SAME file again (idempotence probe)", + "POST with mappingName: 'no_such_mapping'" + ], + "acceptance": [ + { + "clause": "foreign headers land on the mapped fields — 'Full Name' → the name field, 'Channel' → the source field — per the named mapping, not positional guessing", + "oracle": "api", + "verify": "created rows carry the CSV values on the mapped target fields", + "evidence": "the reads" + }, + { + "clause": "re-importing the same file is idempotent (upsert on the mapping's key, e.g. email) — no duplicate rows", + "oracle": "api", + "verify": "row count unchanged after the second import", + "evidence": "before/after counts" + }, + { + "clause": "an unknown mapping name fails loudly with a located error — never a silent positional fallback", + "oracle": "api", + "verify": "the bad-mapping response is a 4xx naming the missing mapping", + "evidence": "the response" + } + ], + "negative": [ + "a duplicate-creating re-import (upsert key ignored) is a FAIL; so is a silent positional import when the named mapping is missing" + ], + "traps": [ + "seed-data-thin" + ], + "source": [ + "examples/app-showcase/src/data/mappings/ (showcase_inquiry_feed)", + "content/docs tour_data (named mapping claim)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — the coverage.json `mapping` waiver was STALE (showcase ships showcase_inquiry_feed); un-waived", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.adhoc-filter-sort-builder", + "title": "User-built FilterBuilder/SortBuilder toolbar merges with the view filter, restores across nav, and reproduces rows from the URL", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": [ + "seeded admin (admin@objectos.ai / admin123)", + "a second account signed up on the same browser (for the user-scoping probe — sys_user rows come from sign-up)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_task with its saved views (examples/app-showcase/src/ui/views/task.view.ts) — the in_progress view carries a stored filter (status equals in_progress) so a toolbar condition can be proven to MERGE with, not replace, the view filter", + "10 seeded tasks spanning statuses/priorities/estimate_hours so a 2-condition toolbar filter returns a non-trivial subset (examples/app-showcase/src/data/seed/index.ts)" + ] + }, + "steps": [ + "sign in as admin; open showcase_task and switch to the in_progress saved view (stored filter status=in_progress); screenshot", + "open the FilterBuilder toolbar; add 2 conditions (e.g. priority = high, estimate_hours > 4); open the SortBuilder and add a sort (due_date desc); capture the data request", + "assert the request $filter is the MERGE of the view's stored rule AND the two toolbar conditions (buildEffectiveFilter/mergeFilterNodes), and $orderby carries the toolbar sort; the returned rows satisfy view-filter AND toolbar conditions", + "navigate away via an in-app nav link to another object (the link carries no query string, so the URL state is dropped), then navigate back to showcase_task in_progress; screenshot; confirm the toolbar filter + sort restored", + "read localStorage key list-filters:<userId>:showcase_task:<viewId> and confirm it embeds the signed-in user id", + "sign out; sign in as the SECOND account; open the same object+view; confirm the first account's cached toolbar filter does NOT appear", + "userFilters/URL: where UserFilters are hosted (an interface page or the userFilters surface — NOT a bare object list, ADR-0053), apply a quick-filter; copy the URL; open it in a fresh tab; confirm the uf_<field> params (comma-joined, URI-encoded) reproduce the same rows and uf__tab carries the active preset (ADR-0047)", + "negative sweep: confirm userFilters (quick-filter chips / uf_* params) do NOT leak onto the bare object list view" + ], + "acceptance": [ + { + "clause": "the FilterBuilder toolbar conditions MERGE with the view's stored filter — the data request $filter carries BOTH the view rule AND the user conditions (buildEffectiveFilter over baseFilter + userFilter + normalized per-field conditions), never a replacement that drops the view filter", + "oracle": "network", + "verify": "captured GET /api/v1/data/showcase_task with $filter containing the view's status=in_progress node AND the two toolbar conditions merged (objectui plugin-list/src/ListView.tsx buildEffectiveFilter → mergeFilterNodes); rows returned satisfy every clause", + "evidence": "the request URL + first-page rows" + }, + { + "clause": "the SortBuilder sort reaches the request as $orderby and the rows come back server-ordered (client re-sorting is not the oracle)", + "oracle": "network", + "verify": "the request carries $orderby for the toolbar sort field/direction; response row order matches", + "evidence": "the request URL + row order" + }, + { + "clause": "the toolbar filter + search survive a FULL in-app navigation away and back — restored from localStorage (URL params alone are lost on an in-app nav link that carries no query string; listFilterStorage exists precisely to bridge that)", + "oracle": "dom", + "verify": "after a screenshot confirms the list rendered, the FilterBuilder shows the two conditions and the SortBuilder the sort; the request re-issued on return carries the same merged $filter (app-shell/src/views/listFilterStorage.ts)", + "evidence": "before/after screenshots + the re-issued request" + }, + { + "clause": "the localStorage cache is USER-SCOPED — the key embeds the user id (list-filters:<userId>:<object>:<view>) so a second account on the same browser never reads the first account's cached filters (a filter value can be sensitive)", + "oracle": "dom", + "verify": "read the storage key as account A, then sign in as account B and confirm B's view opens with no A-authored toolbar filter; anon falls to its own 'anon' bucket (buildListFilterKey)", + "evidence": "the two accounts' storage keys + B's clean toolbar" + }, + { + "clause": "uf_* URL params make a filtered list shareable and reproducible: uf_<field> params (comma-joined, each URI-encoded) reproduce the same rows on a fresh load and uf__tab carries the active preset (ADR-0047)", + "oracle": "network", + "verify": "opening the copied URL fresh issues the same filtered data request and returns the same rows (app-shell/src/views/userFilterUrlState.ts parseUserFilterParams/applyUserFilterParams)", + "evidence": "the shared URL + the reproduced rows" + } + ], + "negative": [ + "a toolbar filter that REPLACES the view's stored filter (rows appear that the view filter should exclude) is a FAIL — buildEffectiveFilter merges, it does not overwrite the base", + "userFilters (quick-filter chips / uf_* params) leaking onto a bare OBJECT list view is a FAIL — ADR-0053 suppresses them there by design (filter elements belong to interface pages; the list-view-capabilities item flags the same regression)", + "a cached toolbar filter from account A visible to account B on the same browser is a FAIL — the key embeds the user id precisely to prevent that leak", + "an incomplete FilterBuilder row emitted as [field, op, ''] (which matches only empty and silently excludes everything) instead of being dropped is a FAIL — convertFilterGroupToAST skips valueless rows (#1964)" + ], + "traps": [ + "hydration-race", + "shared-browser-tab", + "stale-console-bundle" + ], + "automated": { + "kind": "e2e", + "ref": "objectui: e2e/live/user-filters.spec.ts, e2e/live/saved-view-filter.spec.ts" + }, + "source": [ + "objectui: packages/plugin-list/src/ListView.tsx (buildEffectiveFilter, convertFilterGroupToAST, mergeFilterNodes; $filter + $orderby assembly)", + "objectui: packages/app-shell/src/views/listFilterStorage.ts (user-scoped localStorage key, debounced write, clear)", + "objectui: packages/app-shell/src/views/userFilterUrlState.ts (uf_* params, ADR-0047)", + "ADR-0053 (userFilters belong to interface pages, suppressed on object list views); examples/app-showcase/src/ui/views/task.view.ts", + "cross-ref: records-forms.list-view-capabilities (saved-view-filter / userFilters negative)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — deep-test the ad-hoc FilterBuilder/SortBuilder toolbar: view-filter merge + $orderby, localStorage nav-restore (user-scoped), uf_* URL reproduction, ADR-0053 leak negative", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.calendar-interactions", + "title": "Calendar is interactive: drag reschedules via PATCH, a failing PATCH reverts with an error, mode switches re-render, empty-day click quick-creates", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the showcase_task Calendar view (examples/app-showcase/src/ui/views/task.view.ts: named 'Calendar' view + allowedVisualizations calendar, calendar.startDateField due_date, titleField title, colorField status)", + "10 seeded tasks carrying due_date values so cards land on distinct days (examples/app-showcase/src/data/seed/index.ts)" + ] + }, + "variants": [ + "month-view card drag (move) → PATCH due_date", + "week/day time-grid move → PATCH start (+ end)", + "resize-end → PATCH end only", + "empty-day/slot click → quick-create" + ], + "steps": [ + "open showcase_task; switch to the Calendar view; wait for cards to render (screenshot FIRST)", + "drag one task card to another day; capture the PATCH /api/v1/data/showcase_task/<id> carrying the new due_date; re-read the record over the API", + "reload the view; confirm the card renders on the new day from server state", + "force a FAILING reschedule (drive as a persona lacking edit, or drag a record the server rejects); observe the optimistic move, then the rollback to the original day AND an error toast", + "switch month → week → day modes; confirm the same records re-render placed by their date fields", + "click an empty day cell; a quick-create dialog opens pre-filled with that date; submit; capture the create and re-read the new row's due_date = the clicked day", + "capture one screenshot per interaction variant" + ], + "acceptance": [ + { + "clause": "a card drag issues the record PATCH carrying the new date field(s) and the API re-read confirms the persisted value — pixels are not the oracle, the row is", + "oracle": "api", + "verify": "captured PATCH /api/v1/data/showcase_task/<id> with the new due_date (ObjectCalendar handleEventDropDefault → dataSource.update); GET re-read matches; reload re-renders from server state", + "evidence": "PATCH trace + re-read + post-reload screenshot" + }, + { + "clause": "a FAILING PATCH REVERTS the optimistic move AND surfaces an error toast — never a silent snap-back that hides the failure (a 403 RLS denial is the common case)", + "oracle": "dom", + "verify": "after screenshot, the card returns to its original day and a visible error toast appears (ObjectCalendar rolls back setData(prevData) + toast.error, cloud#864); the geometry is unit-pinned by CalendarView.dnd.test.tsx", + "evidence": "before/after screenshots + the failed PATCH trace" + }, + { + "clause": "month/week/day mode switches re-render the SAME records against their date fields (not a blank grid, not a grid fallback)", + "oracle": "dom", + "verify": "after screenshots at each mode, the seeded tasks appear placed by due_date/start; spot-check 2 against their API-read dates", + "evidence": "per-mode screenshots + 2 API reads" + }, + { + "clause": "empty-day quick-create inserts a REAL row: the dialog pre-fills the clicked date, submit issues dataSource.create, and the API re-read shows the new row with its date field = the clicked day", + "oracle": "api", + "verify": "capture the create request and GET the new row; the start/due date equals the clicked cell's date (ObjectCalendar quick-create → dataSource.create)", + "evidence": "create trace + the row read" + } + ], + "negative": [ + "a drag that repaints the card but lands NO PATCH (or a 4xx PATCH while the card keeps the new day) is a FAIL — the calendar can lie visually, the same class as the gantt §8 rows", + "a failed reschedule that silently snaps back with NO error surfaced is a FAIL — the code explicitly rolls back AND toasts; a silent revert would hide a real RLS denial" + ], + "traps": [ + "hydration-race", + "automation-input" + ], + "automated": { + "kind": "unit", + "ref": "objectui: packages/plugin-calendar/src/CalendarView.dnd.test.tsx (move/resize/time-grid drag geometry)" + }, + "source": [ + "objectui: packages/plugin-calendar/src/ObjectCalendar.tsx (handleEventDropDefault optimistic update + rollback + toast; empty-day quick-create → dataSource.create)", + "objectui: packages/plugin-calendar/src/CalendarView.tsx + CalendarView.dnd.test.tsx", + "examples/app-showcase/src/ui/views/task.view.ts (Calendar view: startDateField due_date, colorField status)", + "cross-ref: records-forms.view-type-gallery (calendar render-only), records-forms.kanban-drag-persistence / gantt-interactions (sibling drag-persist items)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — calendar was render-only in view-type-gallery; this deep-tests the drag→PATCH→revert chain, mode switches, and empty-day quick-create", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.concurrent-edit-conflict", + "title": "Concurrent edits collide loudly (OCC 409 + conflict dialog), never silent last-write-wins; inline two-surface edit is ONE atomic OCC-guarded save", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": [ + "seeded admin (admin@objectos.ai / admin123) in TWO independent browser sessions (distinct auth cookies), 'A' and 'B'" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "any writable object whose rows carry an updated_at OCC token (e.g. showcase_account or showcase_task) — the form reads updated_at and sends it as If-Match (objectui plugin-form/src/occSave.tsx; framework packages/metadata-protocol/src/protocol.ts updateData If-Match handling)", + "a record with both a details-body field and a header-highlight field for the two-surface inline-edit fold-in (RecordDetailView highlight fields)" + ] + }, + "steps": [ + "open the SAME record's detail in session A and session B (both hold the same updated_at)", + "in A: edit one field, Save; capture the PATCH carrying If-Match: <updated_at> → 200; the record's updated_at advances", + "in B (still holding the STALE updated_at): edit the same field, Save; capture the response", + "assert B receives 409 CONCURRENT_UPDATE and the conflict dialog appears (Reload latest / Overwrite) — NOT a silent overwrite, NOT a bare error", + "in B choose Reload → the form refetches and shows A's value", + "in B re-edit and choose Overwrite → the retry re-keys ifMatch to the version the 409 reported → 200; re-read shows exactly one winner (B's value)", + "INLINE two-surface (§E1): on the detail, enter the shared inline-edit session; edit one details-BODY field AND one header-HIGHLIGHT field; confirm ONE Save bar; Save; capture the single PATCH and its keys + headers", + "stale-ifMatch inline path: repeat the inline save holding a stale updated_at (a concurrent write landed between) and capture the outcome" + ], + "acceptance": [ + { + "clause": "the losing save is REFUSED with 409 CONCURRENT_UPDATE server-side — never a silent last-write-wins that overwrites A with no signal", + "oracle": "api", + "verify": "B's PATCH returns HTTP 409 with code CONCURRENT_UPDATE (framework rest-server.ts error mapping: error.code CONCURRENT_UPDATE / ConcurrentUpdateError → 409); A's value is intact on an independent re-read", + "evidence": "B's 409 response + the intact re-read" + }, + { + "clause": "the conflict surfaces a conflict DIALOG offering Reload/Overwrite (Keep editing) — a structured choice, not a silent overwrite and not a raw stack trace", + "oracle": "dom", + "verify": "after screenshot, the ConcurrentUpdateDialog (plugin-detail) / occSave conflict dialog (plugin-form) renders with the racer's version and the two actions", + "evidence": "the conflict-dialog screenshot" + }, + { + "clause": "Reload DISCARDS B's pending edit and refetches — B's form then shows A's value", + "oracle": "api", + "verify": "after Reload, the form's field equals A's saved value (a fresh GET), and no B write landed", + "evidence": "the refetch read + no-write trace" + }, + { + "clause": "Overwrite re-keys ifMatch to the version the 409 reported and lands EXACTLY ONE winner on re-read — an explicit last-write chosen by the user, not an accident", + "oracle": "api", + "verify": "the overwrite retry carries the 409-reported currentVersion as ifMatch → 200; the final re-read shows B's value, with no lost-update ambiguity (occSave settle/retry path)", + "evidence": "the overwrite PATCH + final re-read" + }, + { + "clause": "inline two-surface edit is ONE atomic save: editing one details-body field AND one header-highlight field drives ONE Save bar and issues ONE PATCH carrying EXACTLY those two keys plus ifMatch = the read updated_at (the draft holds changed keys only — never computed/readonly/untouched fields)", + "oracle": "network", + "verify": "the single PATCH body has exactly the two edited keys and an If-Match header (objectui InlineEditSaveBar dataSource.update(obj,id,draft,{ifMatch:data.updated_at}); InlineEditContext draft = changed keys; pinned family e2e/live/inline-edit-polish-2572.spec.ts)", + "evidence": "the single PATCH body + headers" + }, + { + "clause": "the inline path is OCC-guarded too: an inline save on a stale ifMatch → 409 conflict (same dialog), not a silent overwrite", + "oracle": "api", + "verify": "the stale inline PATCH returns 409 CONCURRENT_UPDATE and the conflict dialog appears", + "evidence": "the 409 + dialog" + } + ], + "negative": [ + "silent last-write-wins — B's save overwriting A with no 409 and no dialog — is THE FAIL this item exists to catch", + "an inline two-surface edit that issues TWO PATCHes (one per surface) or a single PATCH carrying untouched/computed/readonly keys is a FAIL — one atomic save, changed keys only", + "an inline or form save that omits the If-Match header (an unguarded write) is a FAIL — the OCC token must ride or a concurrent overwrite goes undetected" + ], + "traps": [ + "hydration-race", + "automation-input", + "shared-browser-tab", + "stale-console-bundle" + ], + "automated": { + "kind": "unit", + "ref": "objectui: packages/plugin-form/src/occSave.test.tsx (409 handling + overwrite retry); e2e/live/inline-edit-polish-2572.spec.ts (single save bar / atomic inline save)" + }, + "source": [ + "objectui: packages/plugin-detail/src/ConcurrentUpdateDialog.tsx (Reload/Overwrite UX), packages/plugin-form/src/occSave.tsx (If-Match → 409, conflict dialog, re-key overwrite)", + "objectui: packages/plugin-detail/src/InlineEditSaveBar.tsx + packages/react/src/context/InlineEditContext.tsx (one save bar, changed-keys draft, ifMatch)", + "framework: packages/rest/src/rest-server.ts (CONCURRENT_UPDATE → 409 mapping), packages/metadata-protocol/src/protocol.ts updateData (If-Match / expectedVersion)", + "PENDING-GAPS §C concurrent-edit-conflict + §E1 inline-edit atomic two-surface (objectui#2542/2549/2604); cross-ref records-forms.crud-roundtrip" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — deep-test OCC conflict (409 + dialog, reload/overwrite) and fold in the §E1 inline-edit atomic two-surface behavior (ONE save bar / ONE PATCH / ifMatch)", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.record-discussion-mentions", + "title": "Record discussion: an @mention comment reconciles optimistically, persists to sys_comment, interleaves with activity, and pings the mentioned user's bell", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "browser", + "personas": [ + "seeded admin (admin@objectos.ai / admin123) — the comment author", + "a second signed-up user — the @mention TARGET (sys_user rows come from sign-up, not seeds)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "a showcase object with feeds enabled (default — enable.feeds !== false) so the discussion panel mounts and sys_comment writes are accepted (packages/spec/src/data/object.zod.ts feeds default true; plugin-audit enforceFeedsCapability)", + "a record to comment on (e.g. a seeded showcase_task or showcase_account)" + ], + "knownGaps": [ + "the @mention TARGET and the bell/notification clauses need a SECOND signed-up user — a fresh single-user boot cannot exercise them; record those clauses blocked(fixture) and run the optimistic-reconcile + sys_comment + activity-interleave clauses as the single author" + ] + }, + "steps": [ + "as admin, open a record detail with the discussion panel (feeds enabled); screenshot", + "type a comment containing an @mention (the mention autocomplete offers mentionSuggestions); post it", + "observe the OPTIMISTIC comment row appear immediately; capture the sys_comment create", + "after the feed refetch, confirm the optimistic row reconciles with the server row (same id → union-by-id merge, no duplicate, no phantom)", + "read sys_comment over the API: GET /api/v1/data/sys_comment?$filter for the record's thread_id, ordered by created_at asc — the posted comment is present", + "perform an attributable change on the record (e.g. an edit) and confirm the resulting sys_activity row interleaves into the SAME feed, oldest-first", + "as the mentioned second user, open the notification bell — it gains the mention notification with a deep link back to the record; unreadCount increments; mark-as-read drops it", + "confirm a NON-mentioned user's bell does NOT gain the notification (recipient scoping)" + ], + "acceptance": [ + { + "clause": "the posted comment PERSISTS to sys_comment (thread_id-scoped, created_at-ordered) and the API read returns it — a success toast is not the oracle, the row is", + "oracle": "api", + "verify": "GET /api/v1/data/sys_comment filtered by the record's thread_id returns the comment with its body (RecordDetailView sys_comment fetch: dataSource.find('sys_comment', {$filter:{thread_id}, $orderby:{created_at:'asc'}}))", + "evidence": "the sys_comment read" + }, + { + "clause": "the OPTIMISTIC row reconciles with the server row by id — no duplicate, no phantom (the create uses the same id the refetch returns; union-by-id keeps one)", + "oracle": "dom", + "verify": "after screenshot, the feed shows exactly ONE row for the posted comment across the post→refetch transition (RecordDetailView mergeFeedRows: Map by String(id), server copy wins on the same key)", + "evidence": "post + post-refetch feed screenshots" + }, + { + "clause": "sys_activity rows INTERLEAVE into the same discussion feed, oldest-first, alongside the comments (one feed, two tables)", + "oracle": "dom", + "verify": "after an attributable change, its sys_activity row appears in time order among the comment rows (mergeFeedRows sorts by createdAt; recordActivityFeed.ts maps sys_activity.type → FeedItemType)", + "evidence": "the interleaved feed screenshot + the sys_activity read" + }, + { + "clause": "the mentioned user's bell gains the notification with a WORKING deep link to the record, and unreadCount increments; mark-as-read / mark-all-read drop the count", + "oracle": "dom", + "verify": "as the mentioned user, the bell shows the new mention notification, its link navigates to the commented record, and reading it decrements unreadCount (collaboration/useMentionNotifications)", + "evidence": "bell screenshots before/after + the deep-link navigation" + }, + { + "clause": "notifications are RECIPIENT-scoped both sides: only the @mentioned user's bell gains it (addNotification is gated recipientId === currentUserId); a non-mentioned user's bell does not", + "oracle": "dom", + "verify": "the mentioned user sees it; a second, non-mentioned session does not (useMentionNotifications recipient gate)", + "evidence": "both users' bells" + } + ], + "negative": [ + "an optimistic comment that stays as a SECOND row after the server row lands (dupe) or vanishes entirely (phantom) is a FAIL — mergeFeedRows exists to keep exactly one", + "a mention notification delivered to a NON-mentioned user is a FAIL — the recipientId gate is the boundary", + "feeds:false must HIDE the panel, SKIP the sys_comment fetch, AND the server must reject new comments with 403 FEEDS_DISABLED — a silent no-op that accepts a comment nowhere-readable is a FAIL" + ], + "traps": [ + "hydration-race", + "automation-input", + "seed-data-thin" + ], + "source": [ + "objectui: packages/plugin-detail/src/{RecordChatterPanel,CommentInput,MentionAutocomplete,extractMentions}.tsx (compose + @mention)", + "objectui: packages/app-shell/src/views/RecordDetailView.tsx (mergeFeedRows union-by-id, sys_comment + sys_activity fetch/merge, mentionSuggestions), packages/plugin-detail/src/renderers/recordActivityFeed.ts (activity→feed map)", + "objectui: packages/collaboration/src/useMentionNotifications.ts (recipient-scoped bell, unreadCount)", + "framework: packages/spec/src/data/object.zod.ts (enable.feeds default true → FEEDS_DISABLED); PENDING-GAPS §B record-discussion-mentions" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — deep-test the record discussion: optimistic reconcile, sys_comment persistence, activity interleave, recipient-scoped mention bell", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.grid-personalization", + "title": "Grid personalization persists across reload: column resize/reorder/pin, row-height, group-by (with API-true totals), row-color", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_task grid view with a groupable field (status) and a summarizable field (estimate_hours) — examples/app-showcase/src/ui/views/task.view.ts; 10 seeded tasks across statuses (examples/app-showcase/src/data/seed/index.ts)" + ] + }, + "variants": [ + "column-resize", + "column-reorder", + "column-pin", + "row-height", + "group-by", + "row-color" + ], + "steps": [ + "open showcase_task grid; screenshot", + "resize a column, reorder two columns, pin a column, and set a row-height mode (compact/short/medium/tall/extra_tall)", + "reload; confirm the columnState (widths, order, pinned) and row-height restored from localStorage grid-columns-showcase_task-<viewId> (or the persisted view override via dataSource.updateViewConfig)", + "group-by status; confirm per-group headers appear and each group shows its summarizeField (estimate_hours) total; clear grouping and confirm the headers are removed cleanly", + "cross-check one group's total against a direct API aggregate: GET /api/v1/data/showcase_task grouped client-side from the raw rows", + "set a row-color rule; confirm rows paint by the rule (useRowColor)", + "reload once more; confirm every personalization survives; capture a screenshot per variant" + ], + "acceptance": [ + { + "clause": "PER-VARIANT: column resize + reorder + pin persist across a reload — the columnState (widths/order/pinned) restores from localStorage (grid-columns-<object>-<view>) or a persisted view override; a repaint that does NOT survive reload fails that variant", + "oracle": "dom", + "verify": "after a screenshot confirms render, the reloaded grid shows the same widths/order/pinned column (ObjectGrid columnState priority: props override > localStorage > empty; saveColumnState writes both)", + "evidence": "before/after-reload screenshots + the storage value" + }, + { + "clause": "row-height mode persists across reload (one of compact/short/medium/tall/extra_tall)", + "oracle": "dom", + "verify": "the reloaded grid renders at the chosen density (ObjectGrid rowHeightMode)", + "evidence": "before/after screenshots" + }, + { + "clause": "group-by renders per-group headers and CLEARS cleanly, and the grouped totals MATCH a direct API aggregate of the grouping/summary field — grouping summarizes the real rows, not the rendered page", + "oracle": "api", + "verify": "compare a group's summarizeField total against GET /api/v1/data/showcase_task aggregated client-side (ObjectGrid grouping + useColumnSummary); clearing grouping removes the headers", + "evidence": "board screenshot + the aggregate check" + }, + { + "clause": "row-color rules paint rows by the rule (useRowColor), and survive a reload with the rest of the personalization", + "oracle": "screenshot", + "verify": "after reload the colored rows match the rule against the seeded values", + "evidence": "post-reload screenshot" + }, + { + "clause": "the column-state store is per-browser and deliberately NOT user-scoped (cosmetic), UNLIKE the runtime filter store — so a shared column layout across accounts on one browser is by-design, not a leak to fail on", + "oracle": "dom", + "verify": "the key is grid-columns-<object>[-<view>] with no user id (listFilterStorage.ts documents the deliberate asymmetry: widths cosmetic, filter values sensitive)", + "evidence": "the storage key" + } + ], + "negative": [ + "a personalization that repaints but does NOT survive reload is a FAIL — the persistence, not the repaint, is the contract", + "a group total that disagrees with the API aggregate is a FAIL — the grouping must summarize the real matching rows, not just the rendered page" + ], + "traps": [ + "hydration-race", + "stale-console-bundle" + ], + "automated": { + "kind": "unit", + "ref": "objectui: packages/plugin-grid/src/__tests__/{groupedPagination,groupedBooleanLabel,inlineEditPersistence}.test.tsx" + }, + "source": [ + "objectui: packages/plugin-grid/src/ObjectGrid.tsx (columnState + saveColumnState localStorage grid-columns-*, rowHeightMode, schema.grouping, useRowColor, useColumnSummary)", + "objectui: packages/components/src/renderers/complex/data-table.tsx (the underlying table)", + "objectui: packages/app-shell/src/views/listFilterStorage.ts (documents grid-columns-* is NOT user-scoped by design)", + "examples/app-showcase/src/ui/views/task.view.ts" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — deep-test grid personalization persistence (columns/row-height/grouping/row-color) with API-true grouping totals", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.saved-view-management", + "title": "Admin saved-view lifecycle: create kanban via dialog, rename/set-default/pin/delete each hit the meta overlay and survive reload; non-admin affordances absent and server-refused", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "personas": [ + "seeded admin (admin@objectos.ai / admin123)", + "a non-admin user (e.g. bound to showcase_member_default) — sign one up; sys_user rows cannot be seeded" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "a showcase object with a groupable field for the kanban groupByField (showcase_task, status) — examples/app-showcase/src/ui/views/task.view.ts", + "the console Create View / Manage Views dialogs (objectui CreateViewDialog.tsx, ManageViewsDialog.tsx)" + ], + "knownGaps": [ + "the non-admin refusal clauses need a signed-up non-admin user bound to a member set — record them blocked(fixture) on a single-user boot" + ] + }, + "variants": [ + "create-kanban", + "rename", + "set-default", + "pin", + "delete" + ], + "steps": [ + "as admin, open showcase_task; open the Create View dialog; choose kanban; set groupByField = status; create — capture PUT /api/v1/meta/view/<name> (meta.saveItem) carrying {type:'kanban', kanban:{groupByField:'status'}}", + "reload; confirm the new kanban view is live and groups by status", + "rename the view via Manage Views; capture the PUT; reload confirms the new label", + "set the view as default (unsets the prior default); capture the PUT(s); reload confirms the default moved", + "pin the view; capture the PUT (isPinned); reload confirms it pinned", + "delete the view; capture DELETE /api/v1/meta/view/<name>; reload confirms it is gone", + "as the non-admin, open the same object; confirm the create-view and manage-views (rename/default/pin/delete) affordances are ABSENT", + "as the non-admin, forge a direct PUT /api/v1/meta/view/<name>; capture the refusal" + ], + "acceptance": [ + { + "clause": "creating a kanban view via CreateViewDialog persists a PUT /api/v1/meta/view/<name> carrying the type and its config (kanban.groupByField), and the view is LIVE after reload — a local repaint that never reached the overlay is not enough", + "oracle": "network", + "verify": "captured PUT /api/v1/meta/view/<name> (data-objectstack createView → client.meta.saveItem('view', name, spec); framework route PUT /api/v1/meta/:type/:name, client meta.saveItem); reload re-reads it via listViews", + "evidence": "the PUT payload + post-reload view list" + }, + { + "clause": "rename / set-default / pin / delete each issue their meta write and are live after reload; set-default UNSETS the prior default (updateView isDefault flips all others off)", + "oracle": "network", + "verify": "one PUT per rename/pin/isDefault (updateView read-merge-write) and a DELETE /api/v1/meta/view/<name> for delete (deleteView → meta.deleteItem); reload reflects each", + "evidence": "the four traces + post-reload states" + }, + { + "clause": "the non-admin lacks the mutation affordances: the create-view control and the Manage Views rename/default/pin/delete actions are ABSENT (ManageViewsDialog suppresses mutation affordances for system/read-only)", + "oracle": "dom", + "verify": "after a screenshot confirms the object opened, the DOM shows no create-view / manage mutation controls for the non-admin", + "evidence": "the non-admin screenshot" + }, + { + "clause": "the SERVER is the boundary: a forged PUT /api/v1/meta/view/<name> by the non-admin is refused (4xx) — UI absence alone is courtesy", + "oracle": "api", + "verify": "the direct non-admin PUT returns a 403-class refusal and no overlay view is created (RUNNER rule 4, ADR-0057 D10)", + "evidence": "the forged-request refusal" + } + ], + "negative": [ + "a view mutation that repaints locally but does NOT survive reload (never reached the meta overlay) is a FAIL", + "a non-admin able to persist a meta view via a forged PUT is a FAIL — both sides of the gate", + "set-default that leaves TWO views flagged default (the prior one not unset) is a FAIL" + ], + "traps": [ + "hydration-race", + "wrong-persona" + ], + "source": [ + "objectui: packages/app-shell/src/views/CreateViewDialog.tsx (kanban groupByField), packages/plugin-view/src/ManageViewsDialog.tsx (rename/default/pin/delete affordances, read-only suppression)", + "objectui: packages/app-shell/src/views/ObjectView.tsx (updateView/deleteView wiring), packages/data-objectstack/src/index.ts (createView/updateView/deleteView → meta.saveItem/deleteItem)", + "framework: packages/rest/src/rest-route-ledger.ts:94-95 (PUT/DELETE /api/v1/meta/:type/:name)", + "PENDING-GAPS §C saved-view-management; cross-ref studio-authoring.view-authoring-live (distinct — this is the runtime end-user/admin lifecycle)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — deep-test the saved-view lifecycle (create-kanban/rename/default/pin/delete) against the meta overlay, with the non-admin both-sides gate", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.bulk-select-all-matching", + "title": "Select-all-matching covers every matching id across pages (server total, not the rendered page); clear resets both selection sources", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "a list whose filtered result exceeds one page: showcase_contact filtered to Northwind's account has 26 rows (2 named + 24 'Prospect NN'), authored to exceed a page (examples/app-showcase/src/data/seed/index.ts, objectui#2711)", + "a bulk action registered on the object so the BulkActionBar and its aggregate/per-record dispatch are exercisable (objectui plugin-grid BulkActionBar)" + ] + }, + "steps": [ + "open the showcase_contact list filtered so the match set exceeds one page (e.g. account = Northwind → 26 rows); screenshot; note page shows N rows, count bar shows server total M (M > N)", + "select the whole visible page; the cross-page banner appears offering 'Select all M matching'", + "click select-all-matching; the banner reads 'All M matching records are selected'", + "dispatch a bulk action; capture the requests — resolveBulkRows fans out a paged find (dropping $top/$skip, paging 500) collecting every matching id ACROSS pages", + "cross-check the collected id count against a direct filtered API count: GET /api/v1/data/showcase_contact?$filter=...&$top=0 (or the response total)", + "confirm an AGGREGATE action carries every matching id in params._selectedIds; a per-record action fans out one dispatch per matching id", + "click clear; confirm both selection sources reset (the toolbar empties AND the row checkboxes clear)" + ], + "acceptance": [ + { + "clause": "the cross-page banner reports the SERVER total (totalMatching from the find result.total), not the rendered page count — and only appears when the whole page is selected and more matching rows exist off-screen", + "oracle": "network", + "verify": "the banner's count equals the data request's response total (BulkActionBar totalMatching; ObjectGrid captures result.total into totalMatching)", + "evidence": "banner screenshot + the find response total" + }, + { + "clause": "select-all-matching resolves EVERY matching id across pages: the dispatched set equals a direct filtered API count, not just the visible page (paged fan-out with $top/$skip dropped, capped at 5000)", + "oracle": "api", + "verify": "count the ids handed to the executor (resolveBulkRows collected set) vs GET /api/v1/data/showcase_contact filtered total; they match (up to the HARD_CAP)", + "evidence": "the collected-id count + the filtered API count" + }, + { + "clause": "an AGGREGATE bulk action issues ONE dispatch carrying the full matching id set in params._selectedIds; a per-record action fans out one dispatch per matching id", + "oracle": "network", + "verify": "count captured POSTs against the match-set size per mode (ObjectGrid dispatchBulkAction → resolveBulkRows → params._selectedIds)", + "evidence": "the dispatch trace(s)" + }, + { + "clause": "clear resets BOTH selection sources — the toolbar selectedRows AND the data-table row checkboxes (selectionResetKey) — so no ticked rows are stranded with no toolbar to act on them (#3056)", + "oracle": "dom", + "verify": "after clear, both the toolbar and the row checkboxes are empty (resetSelection: setSelectedRows([]) + setSelectAllMatching(false) + bump selectionResetKey)", + "evidence": "post-clear screenshot" + }, + { + "clause": "the fan-out is bounded (HARD_CAP 5000): a match set beyond the cap is handled coherently (truncated to the cap, not silently claiming to cover everything) — note the weakness in evidence where the set is large", + "oracle": "network", + "verify": "the collected set never exceeds 5000; where the match set is larger, the run records the cap rather than asserting full coverage", + "evidence": "the collected-set size vs the server total" + } + ], + "negative": [ + "a 'select all matching' that only acts on the RENDERED page (ids = page rows, not the server match set) is THE FAIL this item extends list-view-capabilities (page-local bulk) to catch", + "a banner total that disagrees with the filtered API count is a FAIL", + "a clear that leaves the row checkboxes ticked while the toolbar empties (or vice versa) is a FAIL (#3056 drift)" + ], + "traps": [ + "hydration-race", + "automation-input" + ], + "automated": { + "kind": "unit", + "ref": "objectui: packages/plugin-grid/src/__tests__/{objectBulkActionDispatch,BulkActionBar,bulkActionRefresh}.test.tsx" + }, + "source": [ + "objectui: packages/plugin-grid/src/ObjectGrid.tsx (selectAllMatching, resolveBulkRows paged fan-out + HARD_CAP, resetSelection #3056, totalMatching), packages/plugin-grid/src/components/BulkActionBar.tsx (cross-page banner)", + "examples/app-showcase/src/data/seed/index.ts (Northwind 26-contact fixture, objectui#2711)", + "PENDING-GAPS §C bulk-select-all-matching; cross-ref records-forms.list-view-capabilities (bulk-actions, page-local)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — deep-test cross-page select-all-matching: server total, across-pages id coverage vs a filtered API count, clear-resets-both (extends the page-local bulk in list-view-capabilities)", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.lookup-picker-create-new", + "title": "Lookup picker quick-create: a user-facing zero-hit lookup opens the referenced create form and adopts the new id; system references offer no quick-create", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "personas": [ + "seeded admin (admin@objectos.ai / admin123) — holds create on the referenced object" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "a user-facing lookup with allowCreate: showcase_business_unit.parent (Field.lookup('showcase_business_unit', {allowCreate:true})) or showcase_category.parent (allowCreate:true) — examples/app-showcase/src/data/objects/{business-unit,category}.object.ts", + "a system/user-directory reference for the negative side (any sys_/cloud_/ai_ reference, or a user/users directory field) — the picker must NOT offer quick-create there" + ] + }, + "steps": [ + "open New Business Unit (or New Category); open the parent lookup picker", + "type a search term with ZERO hits; confirm the picker issues a SCOPED server find against the referenced object and shows a Create-new affordance (labelled with the typed name)", + "click create-new; the referenced object's FULL create form opens (via the ActionProvider modal); fill required fields and save", + "confirm the picker ADOPTS the newly created id and the parent form's field now holds it", + "save the parent; re-read the parent over the API — its reference resolves to the new id", + "type a search WITH hits; confirm each keystroke issues a scoped server request (not client filtering of a preloaded set)", + "open a lookup to a system/user-directory reference (sys_/cloud_/ai_ or user/users); confirm NO create-new affordance is offered" + ], + "acceptance": [ + { + "clause": "a zero-hit USER-FACING lookup offers create-new, opening the referenced object's FULL create form (allowCreate is default-on for user-facing relations so a fresh app is not a dead end)", + "oracle": "dom", + "verify": "after a screenshot confirms the picker, the create-new control is present and clicking it opens the referenced object's create form (LookupField isUserFacingReference + handleCreateNew)", + "evidence": "picker screenshot + the create-form open" + }, + { + "clause": "saving the create form makes the picker ADOPT the new id, and the parent save re-reads with the reference resolving to that exact id", + "oracle": "api", + "verify": "GET the parent after save; the lookup field holds the created child's real id (not a placeholder/stale id)", + "evidence": "the parent re-read + the child id" + }, + { + "clause": "typed search issues a SCOPED server find against the referenced object (server-side search, not client-side filtering of a preloaded set)", + "oracle": "network", + "verify": "captured requests carry the search scoped to the referenced object (RecordPickerDialog → DataSource.find with the query)", + "evidence": "the search request traces" + }, + { + "clause": "system references offer NO quick-create: a lookup to a sys_/cloud_/ai_ object or the user/users directory shows no create-new affordance — you must not mint plumbing rows inline", + "oracle": "dom", + "verify": "after screenshot, the system-reference picker has no create-new control (SYSTEM_REFERENCE_RX /^(sys_|cloud_|ai_)/ + USER_DIRECTORY_REFS {user,users}; sys_user matches the RX)", + "evidence": "the system-reference picker screenshot" + } + ], + "negative": [ + "a create-new offered on a sys_/cloud_/ai_ or user-directory reference is a FAIL — isUserFacingReference excludes them by design", + "a picker that filters a preloaded CLIENT set instead of issuing a scoped server search is a FAIL — the unscoped fetch is the defect", + "the parent adopting a stale/placeholder id instead of the created row's real id is a FAIL" + ], + "traps": [ + "hydration-race", + "automation-input" + ], + "automated": { + "kind": "unit", + "ref": "objectui: packages/fields/src/widgets/{LookupField.dependsOn,RecordPickerDialog.filterOptions}.test.tsx (adjacent picker pins)" + }, + "source": [ + "objectui: packages/fields/src/widgets/LookupField.tsx (allowCreate default-on, SYSTEM_REFERENCE_RX / USER_DIRECTORY_REFS, isUserFacingReference, handleCreateNew), packages/fields/src/widgets/RecordPickerDialog.tsx (scoped find)", + "examples/app-showcase/src/data/objects/business-unit.object.ts + category.object.ts (parent allowCreate:true)", + "PENDING-GAPS §B lookup-picker-create-new" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — deep-test lookup quick-create: create-new opens the referenced form + id adoption + parent re-read, scoped search, and the sys_/user-directory no-quick-create both-sides", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.record-edit-undo", + "title": "Record edit undo: toast Undo / Ctrl+Z restores the prior value through the API and logs a revert; undo after a concurrent change must not silently clobber", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "browser", + "personas": [ + "seeded admin (admin@objectos.ai / admin123)", + "a second session (for the concurrent-clobber probe)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "a writable record and an undoable edit path (an inline edit or an action flagged undoable) — objectui react/src/hooks/useGlobalUndo.ts + RecordDetailView toast Undo action; core globalUndoManager" + ] + }, + "steps": [ + "open a record; make an undoable edit to one field; confirm the success toast shows an Undo button", + "click Undo (or press Ctrl+Z); capture the resulting update; re-read the record over the API and confirm the prior value is restored", + "open the History/audit and confirm a revert entry was added (a forward write, not a silent rollback)", + "press Ctrl+Shift+Z (redo); confirm the edited value is restored", + "CONCURRENT-CLOBBER probe: in session A make the undoable edit; in session B change the SAME field and save; back in A, click Undo", + "observe and FLAG whether A's undo carries an OCC guard or silently overwrites B's value" + ], + "acceptance": [ + { + "clause": "undo restores the prior value through the dataSource (a REAL write) and the API re-read shows it — not merely a local UI rollback", + "oracle": "api", + "verify": "after Undo, GET the record; the field equals its pre-edit value (useGlobalUndo executeOp → dataSource.update(objectName, recordId, op.undoData))", + "evidence": "the re-read + the undo update trace" + }, + { + "clause": "both the toast Undo button and Ctrl+Z trigger undo, and Ctrl+Shift+Z redoes — the same globalUndoManager stack", + "oracle": "dom", + "verify": "the toast action and the keyboard shortcut both restore the prior value; redo re-applies the edit (RecordDetailView toast action onClick undoCtl.undo; useGlobalUndo Ctrl+Z / Ctrl+Shift+Z)", + "evidence": "screenshots of both paths" + }, + { + "clause": "the revert is auditable: the History/audit gains a NEW entry for the undo (undo is a forward write, not a hidden state reset)", + "oracle": "api", + "verify": "after screenshot of the History tab, the audit/read shows the revert as its own update", + "evidence": "the History entries + audit read" + }, + { + "clause": "CONCURRENT-CLOBBER (observe + flag): the undo path issues a bare dataSource.update with NO ifMatch, so an undo after a concurrent edit landed in between will overwrite it — verify the observed behavior and record a silent clobber as a finding, not a silent pass", + "oracle": "api", + "verify": "in the two-session probe, read whether A's undo carries an If-Match / triggers a 409, or silently overwrites B's value (useGlobalUndo executeOp: update(op.objectName, op.recordId, data) — no OCC token)", + "evidence": "the undo request headers + B's value before/after A's undo" + } + ], + "negative": [ + "an undo that only repaints the field but does NOT persist (no API write, or the re-read still shows the edited value) is a FAIL — the restore must round-trip the server", + "an undo silently overwriting a concurrent edit with no conflict signal is the behavior to FLAG as an expected-risk finding (the undo path is unguarded) — recording it as a clean pass would hide a lost-update hole" + ], + "traps": [ + "automation-input", + "hydration-race", + "shared-browser-tab" + ], + "automated": { + "kind": "unit", + "ref": "objectui: packages/core/src/actions/__tests__/UndoManager.test.ts (undo/redo stack)" + }, + "source": [ + "objectui: packages/react/src/hooks/useGlobalUndo.ts (executeOp, undo/redo, Ctrl+Z/Ctrl+Shift+Z), packages/app-shell/src/views/RecordDetailView.tsx (success-toast Undo action), @object-ui/core globalUndoManager", + "PENDING-GAPS §C record-edit-undo (concurrent-clobber flag)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — deep-test edit undo: API-true restore via toast/Ctrl+Z, auditable revert, and the unguarded concurrent-clobber observation", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.import-job-undo-cancel", + "title": "Async import job: terminal state + per-row results, undo removes exactly the imported rows, cancel mid-job leaves a coherent partial", + "since": "v17", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "an import-job-capable client wired into the console AND an object with an async-import route (framework packages/rest/src/rest-route-ledger.ts: POST /api/v1/data/:object/import/jobs, /import/jobs/:jobId/{cancel,undo,results})", + "the objectui import specs, which self-gate on IMPORT_CONSOLE_LIVE=1 (console) or a reachable import harness /live.html" + ], + "knownGaps": [ + "the async import + undo/cancel path is gated: e2e/import-console/import-console-undo.spec.ts skips unless IMPORT_CONSOLE_LIVE=1 with an import-job-capable client wired; e2e/import-harness/import-undo.spec.ts skips unless the harness origin serves /live.html — on a stock showcase boot these do not run, so record blocked(fixture)" + ] + }, + "blocked": { + "by": "fixture", + "ref": "IMPORT_CONSOLE_LIVE / import-harness gate — objectui e2e/import-console/import-console-undo.spec.ts self-skips unless IMPORT_CONSOLE_LIVE=1 and an import-job-capable client is wired; PENDING-GAPS §C import-job-undo-cancel" + }, + "steps": [ + "run the pinned specs gated on: IMPORT_CONSOLE_LIVE=1 pnpm exec playwright test e2e/import-console/import-console-undo.spec.ts (and the harness twin e2e/import-harness/import-undo.spec.ts); capture the suite output as the primary evidence", + "when the gate is available: POST /api/v1/data/:object/import/jobs with a marker-tagged CSV; poll GET /api/v1/data/import/jobs/:jobId until terminal; GET /api/v1/data/import/jobs/:jobId/results for per-row results", + "count marker rows via a filtered GET before and after the import; then POST /api/v1/data/import/jobs/:jobId/undo and re-count", + "GET /api/v1/data/import/jobs and confirm the job list distinguishes undoable/non-undoable and reverted state ({ jobId, undoable, revertedAt, createdAt }) so the fresh job is findable", + "start a fresh import and POST /api/v1/data/import/jobs/:jobId/cancel mid-job; inspect the row set for coherence" + ], + "acceptance": [ + { + "clause": "an async import creates an UNDOABLE job that reaches a terminal state and exposes per-row results", + "oracle": "test", + "verify": "the job list shows { jobId, undoable:true, revertedAt:null } and GET /import/jobs/:jobId/results returns per-row outcomes (pinned by import-console-undo.spec.ts / import-undo.spec.ts under their gate)", + "evidence": "the gated spec output + the jobs/results reads" + }, + { + "clause": "undo removes EXACTLY the imported rows — a marker filter count returns to its pre-import value, with no collateral deletion of pre-existing rows", + "oracle": "api", + "verify": "filtered GET marker count: pre-import == post-undo, and post-import == pre-import + imported count (the specs assert record counts at the backend on both sides)", + "evidence": "the three marker counts (pre / post-import / post-undo)" + }, + { + "clause": "cancel mid-job yields a COHERENT partial: whole rows committed or none, never a half-written row (partial row / dangling FK)", + "oracle": "api", + "verify": "after cancel, the committed rows are complete records; no torn row exists; the job reports the cancelled/partial state", + "evidence": "the post-cancel row read + job state" + }, + { + "clause": "the job list distinguishes undoable/non-undoable and reverted state so a run can find the fresh undoable job it created", + "oracle": "api", + "verify": "GET /api/v1/data/import/jobs returns entries with { jobId, undoable, revertedAt, createdAt } (the specs filter on j.undoable && !j.revertedAt)", + "evidence": "the jobs list read" + } + ], + "negative": [ + "an undo that deletes MORE than the imported rows (or leaves some behind) is a FAIL — exactly the imported set, no more, no less (the async threshold vs undo-capture mismatch is the exact bug the specs exist for)", + "a cancel that leaves half-written rows (partial row, dangling FK) is a FAIL — coherent partial only", + "reporting the item PASS off a skipped (gated-out) spec is a FAIL — a skip is blocked(fixture), never a green tick" + ], + "traps": [ + "seed-data-thin", + "dispatcher-vs-hono-route" + ], + "automated": { + "kind": "e2e", + "ref": "objectui: e2e/import-console/import-console-undo.spec.ts, e2e/import-harness/import-undo.spec.ts (both self-gate)" + }, + "source": [ + "framework: packages/rest/src/rest-route-ledger.ts:124-129 (POST /data/:object/import/jobs; /import/jobs/:jobId/{cancel,undo,results}; GET /import/jobs[/:jobId])", + "objectui: e2e/import-console/import-console-undo.spec.ts, e2e/import-harness/import-undo.spec.ts", + "PENDING-GAPS §C import-job-undo-cancel" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — deep-test async import jobs (terminal + per-row results, exact-set undo, coherent cancel); blocked(fixture) on the IMPORT_CONSOLE_LIVE / harness gate, pinned to the self-gating specs", + "ref": "claude/platform-test-checklist-ocwugl" + } ] } ] -} +} \ No newline at end of file diff --git a/docs/qa/platform-checklist/areas/search.json b/docs/qa/platform-checklist/areas/search.json new file mode 100644 index 0000000000..f9afcad653 --- /dev/null +++ b/docs/qa/platform-checklist/areas/search.json @@ -0,0 +1,471 @@ +{ + "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md.", + "area": "search", + "title": "Record search — $search executor, field scoping, RLS composition, pinyin recall, freshness", + "items": [ + { + "id": "search.cross-field-object-search", + "title": "$search is a server-resolved cross-field match: terms AND-ed, fields OR-ed, case-insensitive, select labels mapped to values", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": ["seeded admin (admin@objectos.ai)"], + "fixtures": { + "app": "showcase", + "requires": [ + "the seeded accounts: Northwind (industry 'retail', name does NOT contain 'retail'), Acme Retail (name-hit control), Contoso (cross-term control) — examples/app-showcase/src/data/seed/index.ts" + ] + }, + "steps": [ + "POST /api/v1/data/showcase_account/query with { search: 'retail' } and record the returned names", + "verify the premise before ticking anything: Northwind's NAME must not contain 'retail' (else the multi-field claim proves nothing)", + "POST the same search narrowed with { search: 'retail', searchFields: ['industry'] } — Northwind must still return, positively pinning the hit to the industry field", + "POST { search: 'Retail' } (capitalized label) — the select label→value mapping must still match the stored 'retail' value", + "POST { search: 'retail northwind' } (terms AND across different fields of one row) and { search: 'retail contoso' } (terms that no single row satisfies)", + "capture every response body as the evidence set" + ], + "acceptance": [ + { + "clause": "a term matching only a non-name field returns the row: 'retail' returns Northwind via industry, and the same search restricted to ['industry'] still returns it", + "oracle": "api", + "verify": "Northwind present in both responses; its name verified not to contain the term (premise guard)", + "evidence": "the two response bodies" + }, + { + "clause": "fields are OR-ed within one query: the unrestricted 'retail' search also returns the name-hit control (Acme Retail) alongside the industry hit", + "oracle": "api", + "verify": "both Northwind and a name-containing-'retail' account appear in one result set", + "evidence": "the response body" + }, + { + "clause": "matching is case-insensitive and select labels map to option values: 'Retail' (label case) matches rows storing the value 'retail'", + "oracle": "api", + "verify": "the capitalized search still returns Northwind (optionValuesMatching + raw-value $contains fallback)", + "evidence": "the response body" + }, + { + "clause": "whitespace-separated terms AND: every term must hit some field of the SAME row — 'retail northwind' matches, 'retail contoso' returns neither Northwind nor Contoso", + "oracle": "api", + "verify": "the two multi-term responses split exactly that way", + "evidence": "the two response bodies" + } + ], + "negative": [ + "'retail contoso' returning Contoso (terms OR-ed instead of AND-ed) is a FAIL against the declared matching semantics", + "an empty result for 'retail' means the executor silently dropped $search — the exact pre-ADR-0061 no-op this surface replaced; FAIL, not thin data" + ], + "variants": [ + "multi-term AND", + "cross-field OR", + "case-insensitive $contains", + "select label→value mapping" + ], + "traps": ["seed-data-thin", "dispatcher-vs-hono-route"], + "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/showcase-search.dogfood.test.ts" }, + "source": [ + "packages/qa/dogfood/test/search-conformance.ledger.ts (rows search-executor, search-select-label-mapping — the variants list is the enforced behavior set)", + "packages/objectql/src/search-filter.ts (matching semantics: terms AND-ed, fields OR-ed, case-insensitive, label mapping)", + "packages/objectql/src/engine.ts expandSearchOnAst (the executor site the ledger names)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item transcribed from the search-conformance ledger and its HTTP-level dogfood proof, seeded names (Northwind/Acme Retail/Contoso) verified in the showcase seed", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "search.field-scoped-narrowing", + "title": "$searchFields narrows and can never widen; a name outside the searchable set is 400 INVALID_FIELD at the ingress, never silently dropped", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": ["seeded admin (admin@objectos.ai)"], + "fixtures": { + "app": "showcase", + "requires": ["the same seeded accounts as search.cross-field-object-search"] + }, + "steps": [ + "POST /api/v1/data/showcase_account/query { search: 'retail', searchFields: ['name'] } — the industry-only hit (Northwind) must drop out", + "POST { search: 'retail', searchFields: ['industry'] } — Northwind must remain (narrowing to the right field keeps the hit)", + "POST { search: 'retail', searchFields: ['no_such_field'] } and capture the refusal", + "POST { search: 'retail', searchFields: ['annual_revenue'] } (a REAL field outside the searchable set — numbers are not searchable-textual) and capture the refusal", + "POST { search: 'zhangwei', searchFields: ['__search'] } against showcase_contact — the hidden companion column must be refusable/invisible to overrides, not a client-nameable widening lever", + "GET the OData spelling too: /api/v1/data/showcase_account?$search=retail&$searchFields=name — the protocol layer normalizes both spellings to the same executor" + ], + "acceptance": [ + { + "clause": "narrowing works: restricted to ['name'], the industry-matched row disappears; restricted to ['industry'], it stays", + "oracle": "api", + "verify": "the two narrowed responses split exactly that way", + "evidence": "both response bodies" + }, + { + "clause": "the override can only narrow — a $searchFields name the object cannot scan is 400 INVALID_FIELD at the REST ingress (#4254), for typos AND for real-but-unsearchable fields alike", + "oracle": "api", + "verify": "both bad-override requests answer 400 with code INVALID_FIELD; neither silently returns the unnarrowed superset", + "evidence": "the two refusal bodies" + }, + { + "clause": "the hidden __search companion is invisible to overrides and to responses: naming it in $searchFields is refused, and no record body ever echoes a __search value", + "oracle": "api", + "verify": "the ['__search'] override is refused; record payloads from any search carry no __search key", + "evidence": "the refusal + a sampled record body" + }, + { + "clause": "both wire spellings (bare search/searchFields in the query POST body, $search/$searchFields on GET) reach the same executor with the same verdicts", + "oracle": "api", + "verify": "the GET spelling reproduces the POST results and refusals", + "evidence": "paired responses" + } + ], + "negative": [ + "a bad $searchFields that answers 200 with unnarrowed rows is the widening leak #4254 closed — a projection typo returns extra columns, a search typo returns extra ROWS; FAIL", + "silent dropping of the override (200, results identical to the unrestricted search) is equally a FAIL — refusal must be loud" + ], + "traps": ["dispatcher-vs-hono-route"], + "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/showcase-search.dogfood.test.ts" }, + "source": [ + "packages/qa/dogfood/test/search-conformance.ledger.ts (row search-fields-override: intersection + ingress gate, #4254)", + "packages/metadata-protocol/src/protocol.ts (assertSearchFieldsAreSearchable — 400 INVALID_FIELD; $-spelling normalization)", + "packages/objectql/src/search-filter.ts (resolveSearchFields intersection; companion excluded from resolution — 'invisible to $searchFields overrides and to clients')" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item from the conformance ledger's override row and the #4254 ingress-gate source, including the companion-invisibility clause from search-filter.ts", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "search.rls-both-personas", + "title": "Search honors RLS both ways: a restricted member gets no hits — and no total leakage — from rows they cannot see; the entitled persona finds the same rows", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": ["seeded admin (sees all invoices)", "signed-up member bound to the contributor position (invoice_own_rows RLS: owner == current_user.email)"], + "fixtures": { + "app": "showcase", + "requires": [ + "seeded invoices INV-1001..INV-1008 with owner emails (ada@example.com / linus@example.com / grace@example.com) none of which belong to the test member", + "a signed-up member holding the contributor position — positions are seeded, users are not: sign the member up, then bind contributor (sys_user_position) before the run" + ], + "knownGaps": [ + "the seeded owner emails are metadata-only (users cannot be seeded; they sign up) — no stock login exists for ada@example.com, so the 'restricted member owns SOME rows' side is produced by having the member CREATE an invoice of their own (contributors hold allowCreate), not by borrowing a seeded owner" + ] + }, + "steps": [ + "premise guard first: as the contributor-bound member, GET /api/v1/data/showcase_invoice/<id-of-INV-1003> — it must be invisible (else the search verdict proves nothing)", + "as the member, POST /api/v1/data/showcase_invoice/query { search: 'INV-1003' } and record records + total", + "as admin, run the identical search — INV-1003 must return (the entitled side of the same gate)", + "as the member, create their own invoice (POST /api/v1/data/showcase_invoice, name 'INV-QA-RLS') and search for it — their own rows must remain findable", + "as the member, run a broad paged search { search: 'INV', limit: 2 } and record records/total/hasMore across pages", + "compare the member's paged totals against their full visible row set" + ], + "acceptance": [ + { + "clause": "the restricted member's search returns zero hits for rows their RLS hides, while the identical admin search returns them — both sides of the gate, same query", + "oracle": "api", + "verify": "member: records []; admin: INV-1003 present; the by-id premise guard confirmed invisibility first", + "evidence": "the paired responses + premise read" + }, + { + "clause": "no count leakage: a searched list's total/hasMore derive from the caller-scoped result set — with `search` present the protocol computes a page-local total from the RLS-filtered find (it never runs a raw count for searched lists), and engine.count itself rides the same read middleware (#2737)", + "oracle": "api", + "verify": "the member's paged totals reconcile exactly with the rows they can enumerate; no response reveals the true 8-row population", + "evidence": "the paged responses + reconciliation table" + }, + { + "clause": "the restriction is subtractive, not a blackout: the member still finds rows they own ('INV-QA-RLS' returns for its creator)", + "oracle": "api", + "verify": "the member's own-row search hits", + "evidence": "the response" + }, + { + "clause": "search rides the engine read path, so the RLS composition is structural: $search only ANDs a filter into ast.where before the security middlewares scope it — verified black-box by the persona split above, not assumed", + "oracle": "api", + "verify": "the persona-split evidence set is complete (deny + allow + own-rows); cite expandSearchOnAst + the middleware ordering as the mechanism, the responses as the proof", + "evidence": "the full evidence set" + } + ], + "negative": [ + "any hit (or any total inflation) for the restricted member from an invisible row is an RLS bypass — P1 FAIL, reproduce twice and file", + "running the deny side as admin proves nothing (wrong-persona trap): the guard check must run as the non-privileged member" + ], + "traps": ["wrong-persona", "seed-data-thin"], + "source": [ + "examples/app-showcase/src/security/permission-sets.ts (invoice_own_rows: owner == current_user.email, positions ['contributor'])", + "examples/app-showcase/src/data/seed/index.ts (INV-1001..1008 owner spread; 'ada sees INV-1001/1002's lines but never linus's INV-1003')", + "packages/objectql/src/engine.ts (expandSearchOnAst ANDs into ast.where; count() rides the read middleware — the #2737 total-leak fix)", + "packages/metadata-protocol/src/protocol.ts (searched lists: countable = search == null → page-local total from the scoped find)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: RLS × $search both-personas contract; total-leakage clause grounded in the protocol's page-local total for searched lists and the #2737 count middleware fix, persona provisioning gap recorded honestly", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "search.pinyin-flag-both-sides", + "title": "Pinyin recall is gated end-to-end by OS_SEARCH_PINYIN_ENABLED: on, latin pinyin (full + initials) hits CJK names; off, it does not — while CJK terms keep matching", + "since": "v15.1", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["seeded admin (admin@objectos.ai)"], + "fixtures": { + "app": "showcase", + "requires": [ + "seeded CJK rows: contact 张伟 (zhangwei@huaning.example) and account 华宁科技 — placed in the seed precisely so pinyin recall is demonstrable out of the box", + "the showcase i18n config (supportedLocales includes zh-CN), which makes the flag default ON: unset OS_SEARCH_PINYIN_ENABLED + any zh-* locale → enabled, stamped into the env at serve boot", + "an environment lever for the OFF side: restart the server with OS_SEARCH_PINYIN_ENABLED=false (an explicit value always beats the locale-derived default — truthy is exactly 1/true/on/yes; anything else explicit disables)" + ] + }, + "steps": [ + "on the stock boot (flag auto-ON via zh-CN locale), POST /api/v1/data/showcase_contact/query { search: 'zhangwei' } — full pinyin must return 张伟", + "POST { search: 'zw' } — initials must return 张伟", + "POST { search: '张' } — the CJK original must return 张伟 (source-column hit, no companion involved)", + "POST /api/v1/data/showcase_account/query { search: 'huaning' } and { search: 'hnkj' } — both must return 华宁科技", + "confirm the seeded rows were companion-backfilled on this very boot (rows written by seeds BEFORE hook binding are reconciled by the kernel:bootstrapped backfill — no restart needed) by checking the boot log for the backfill, then verify in the browser that the list quick-search / ⌘K / lookup picker send the same $search and hit", + "restart the server with OS_SEARCH_PINYIN_ENABLED=false and re-run the same queries", + "capture the boot log line proving the plugin went inert" + ], + "acceptance": [ + { + "clause": "flag ON: every latin recall variant hits — full pinyin (zhangwei → 张伟), initials (zw → 张伟), and the account forms (huaning / hnkj → 华宁科技) — each verified per-variant against the API", + "oracle": "api", + "verify": "one query per variant, each returning the seeded CJK row", + "evidence": "per-variant response bodies" + }, + { + "clause": "the recall is additive and scoped to the display/name field: only the resolved display field feeds the companion; other fields are searched via their source columns directly", + "oracle": "api", + "verify": "a latin term matching a NON-name CJK field does not gain pinyin recall (e.g. company 华宁科技 on a contact whose name is latin) — matches come only where the source column or the name-fed companion matches", + "evidence": "the contrast query" + }, + { + "clause": "flag OFF (explicit false, restart): 'zhangwei'/'zw' no longer return 张伟 — the companion is gone from the schema view and the filter never ORs it — while '张' still matches via the source column", + "oracle": "api", + "verify": "the same three queries after the flagged restart split exactly that way, with no errors", + "evidence": "post-restart response bodies" + }, + { + "clause": "OFF is inert end-to-end, not half-disabled: the plugin logs 'OS_SEARCH_PINYIN_ENABLED is off — inert' (no hooks, no backfill, pinyin-pro never imported) — the ADR-0049 no-half-state design: column provisioning and population share the SINGLE decision point resolveSearchPinyinEnabled", + "oracle": "log", + "verify": "the boot log carries the inert line and no backfill activity", + "evidence": "boot log excerpt" + }, + { + "clause": "seeded (pre-hook) rows are recallable on the FIRST boot: the kernel:bootstrapped backfill reconciles rows written before/around hook binding, and existing deployments' stale rows after a restart (#3027 / 15.1 plan G9)", + "oracle": "api", + "verify": "the flag-ON queries above succeed on a fresh boot without any manual rebuild; the backfill ran per the boot log", + "evidence": "fresh-boot query results + log" + } + ], + "negative": [ + "with the flag off, a 500 or error on latin searches is a FAIL — disabling must degrade to no-recall, not to breakage", + "with the flag on, 'zw' matching NOTHING while 'zhangwei' matches means the initials normalizer regressed — per-variant verification exists precisely so one form cannot stand in for the other" + ], + "variants": [ + "full pinyin (zhangwei → 张伟)", + "initials (zw → 张伟)", + "CJK original (张 → 张伟; flag-independent)", + "account full pinyin (huaning → 华宁科技)", + "account initials (hnkj → 华宁科技)", + "flag OFF contrast (latin forms miss, CJK still hits)" + ], + "traps": ["stale-dist", "seed-data-thin"], + "source": [ + "packages/types/src/env.ts (resolveSearchPinyinEnabled — explicit env wins, truthy set {1,true,on,yes}; unset derives from zh-* locales; stampSearchPinyinEnabled at boot)", + "packages/plugins/plugin-pinyin-search/src/pinyin-search-plugin.ts (inert-when-off, before-save hooks, kernel:bootstrapped backfill)", + "packages/objectql/src/search-companion.ts (__search companion: display-field-only materialization, FLS/secret eligibility gate)", + "packages/objectql/src/search-filter.ts (each latin term ORs { __search: { $contains: term } } — purely additive)", + "examples/app-showcase/src/data/seed/index.ts (张伟/华宁科技 seeded for exactly this demo) + examples/app-showcase/objectstack.config.ts (supportedLocales ['en','zh-CN'])", + "docs/plans/release-15.1-test-plan.md E5/G9 (#3027/#3034)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: both sides of the pinyin flag with toggle semantics read from resolveSearchPinyinEnabled's source (explicit-wins + zh-locale default), recall variants pinned to the seeded CJK rows, backfill contract from the plugin source", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "search.freshness-and-empty", + "title": "Search reflects create/update/delete immediately (query-time expansion, no async index) and a no-hit search degrades to a clean empty result, never an error", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": ["seeded admin (admin@objectos.ai)"], + "fixtures": { + "app": "showcase", + "requires": ["write access to showcase_account (admin) for the probe records"] + }, + "steps": [ + "POST /api/v1/data/showcase_account { name: 'Zephyr Search Probe', status: 'prospect' } and IMMEDIATELY POST /api/v1/data/showcase_account/query { search: 'zephyr' }", + "rename the record to 'Quasar Search Probe' (PATCH) and immediately search 'quasar' AND 'zephyr'", + "create a CJK probe contact (name '搜索测试', any email) and immediately search its full pinyin ('sousuoceshi') — the companion recomputes in the SAME save (before-save hook), so no restart may be needed", + "delete the probe records and immediately re-run their searches", + "POST { search: 'qqqzzz-no-such-term' } and record the full response envelope", + "in the browser, type the same no-hit term into the Accounts list quick-search; screenshot the result and capture the console" + ], + "acceptance": [ + { + "clause": "a just-created record is findable in the immediately-following search — Tier-1 $search is a query-time WHERE expansion over the live table (no separate index, no async pipeline), so API-path writes are read-your-writes", + "oracle": "api", + "verify": "the create→search sequence hits with zero wait/retry", + "evidence": "the timestamped request pair" + }, + { + "clause": "an update is reflected immediately and completely: the new term matches, the OLD term stops matching", + "oracle": "api", + "verify": "post-rename, 'quasar' hits and 'zephyr' misses", + "evidence": "both responses" + }, + { + "clause": "a deleted record stops being findable immediately", + "oracle": "api", + "verify": "post-delete searches return no probe rows", + "evidence": "the responses" + }, + { + "clause": "pinyin freshness rides the same write: the __search companion is recomputed by before-save hooks in the SAME save when the display field changes — a freshly created CJK row is pinyin-findable without any restart. The ONLY deferred path is hook-bypassing writes (direct driver/system writes, rows predating the flag), reconciled at the next boot's kernel:bootstrapped backfill or an explicit rebuildSearchCompanion — that is the actual consistency contract, not instant-for-everything", + "oracle": "api", + "verify": "the fresh CJK probe hits by full pinyin immediately; the deferred-path caveat is recorded in the evidence, not glossed", + "evidence": "the CJK probe responses + the caveat note" + }, + { + "clause": "a no-hit search is a clean success: 200 with records [] and total 0 in the declared envelope — never a 4xx/5xx, never an error body", + "oracle": "api", + "verify": "the no-hit response envelope", + "evidence": "the response body" + }, + { + "clause": "the browser renders a designed empty state for a no-hit quick-search — no error toast, no console error, no stale previous rows", + "oracle": "screenshot", + "verify": "screenshot after settle shows the empty state; console capture is clean", + "evidence": "screenshot + console log" + } + ], + "negative": [ + "any wait-and-retry needed for an API-created row to become findable contradicts the query-time-expansion design — investigate as a FAIL (or a driver seam), never normalize it into the steps", + "a no-hit search answering an error envelope (or the browser toasting one) is a FAIL — absence of results is a result" + ], + "traps": ["hydration-race", "shared-browser-tab"], + "source": [ + "packages/objectql/src/search-filter.ts + packages/objectql/src/engine.ts expandSearchOnAst ($search → $or of $contains ANDed into ast.where at query time — no index artifact for latin search)", + "packages/plugins/plugin-pinyin-search/src/pinyin-search-plugin.ts (before-save recompute = write-through; kernel:bootstrapped backfill + rebuildSearchCompanion = the deferred reconcile paths)", + "packages/qa/dogfood/test/search-conformance.ledger.ts (Tier 2 external engines deliberately absent — there is no FTS index to be stale)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: freshness contract stated from source (query-time expansion ⇒ read-your-writes; pinyin companion write-through with boot-backfill for hook-bypassing writes) instead of assuming an index; clean no-hit behavior on both surfaces", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "search.console-global-search", + "title": "The console global search — ⌘K palette, header Search button, and /search page — drives ONE path (GET /api/v1/search): hits group under object headings, RLS hides invisible rows, Enter opens the record, empty input shows recents", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": [ + "seeded admin (admin@objectos.ai — sees all)", + "signed-up member bound to the contributor position (invoice_own_rows RLS — the same restricted persona as search.rls-both-personas)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the objectui console (app-shell) with CommandPaletteProvider — the ⌘K/Ctrl+K accelerator, the header Search button, and the ?palette=1/?cmdk=1 deep-link all drive the SAME idempotent open (ADR-0054 C1/C3)", + "the seeded CJK/pinyin rows 张伟 (zhangwei@huaning.example) and 华宁科技 (examples/app-showcase/src/data/seed/index.ts) so a CJK/pinyin query has a real hit; the search service (pinyin full-text plugin) installed so GET /api/v1/search resolves", + "seeded invoices INV-1001..INV-1008 (owner spread ada/linus/grace) and a signed-up member bound to the contributor position (sys_user_position) so the RLS-parity side has an invisible row (INV-1003) to prove absence — provisioned exactly as in search.rls-both-personas" + ], + "knownGaps": [ + "the /_console bundle is vendored and may be stale — verify the palette/page behavior against current objectui app-shell or a fresh build (stale-console-bundle, RUNNER §2)", + "when the search plugin is ABSENT, searchAll's GET /api/v1/search answers 404 and useRecordSearch degrades to the per-object find({ $search }) fanout — record WHICH path served from the network trace rather than assuming the global endpoint" + ] + }, + "steps": [ + "sign in as admin in the console; open the palette with ⌘K (Cmd/Ctrl+K), screenshot it (data-testid overlay:command-palette), then close and re-open it via the header Search button (data-testid action:command-palette:open) — confirm the SAME overlay opens (idempotent, ADR-0054 C1)", + "with the input EMPTY (after visiting a couple of records first to populate recents), confirm the palette shows the 'Recently viewed' group and fires NO search request", + "type a seeded CJK/pinyin query (e.g. 'zhangwei', '张', or 'huaning') and capture the network request — confirm it is GET /api/v1/search?q=<term> (searchAll), and capture the returned hits", + "screenshot the settled palette and confirm record hits are grouped under per-object headings (issue #3371); read the DOM only after the screenshot", + "press Enter on the top hit (ref-targeted select, not a coordinate click) and confirm navigation to /apps/<app>/<object>/record/<id> and the record page renders", + "from the palette's 'Open full search' item (navigates to <baseUrl>/search), or directly at /apps/<app>/search?q=<term>, repeat the query on the SearchResultsPage and confirm record hits grouped by object plus nav matches; also run a no-hit query and confirm the designed empty state", + "RLS parity: as the restricted member, run GET /api/v1/search?q=INV-1003 (the row their RLS hides) and the same in the palette; then as admin run the identical query — capture both persona responses", + "capture the browser console for the whole session" + ], + "acceptance": [ + { + "clause": "one open path: ⌘K, Ctrl+K, the header Search button (action:command-palette:open), and the ?palette=1/?cmdk=1 deep-link all open the SAME idempotent palette overlay (ADR-0054 C1/C3) — calling open when already open is a no-op, not a toggle-closed", + "oracle": "dom", + "verify": "each affordance yields the overlay:command-palette overlay and the URL carries the palette param; DOM checked only after a screenshot confirms the overlay rendered", + "evidence": "screenshots of the palette opened by each affordance + the URL param" + }, + { + "clause": "the palette calls the platform global-search endpoint: a seeded query issues GET /api/v1/search?q=<term> (searchAll), not merely the per-object find({ $search }) fanout — captured on the wire; if the search plugin is absent, the honest fallback is the fanout and the run records which path served", + "oracle": "network", + "verify": "the request trace shows GET /api/v1/search?q= for the typed query (or, plugin-absent, the per-object find fanout — named explicitly)", + "evidence": "the search request trace" + }, + { + "clause": "CJK/pinyin recall reaches grouped hits: 'zhangwei' / '张' / 'huaning' returns the seeded CJK record(s), listed under the object's heading — record hits grouped per object (#3371), not a flat undifferentiated list", + "oracle": "network", + "verify": "the /api/v1/search response carries the CJK hit; the palette renders it under its object heading (screenshot confirms the grouping before any DOM read)", + "evidence": "the search response + the grouped-palette screenshot" + }, + { + "clause": "Enter navigates to the record: selecting the top hit routes to /apps/<app>/<object>/record/<id> and the record page renders — the palette is a navigator, not a dead list", + "oracle": "screenshot", + "verify": "post-Enter the record page for the selected hit is on screen at the expected route (ref-targeted select rules out the automation-input trap)", + "evidence": "the record-page screenshot + the resolved URL" + }, + { + "clause": "empty input shows recents, not results: with a blank query the palette shows the 'Recently viewed' group (cloud-synced via sys_user_preference) and fires NO record-search request (minLength 2 guards the wire)", + "oracle": "network", + "verify": "no GET /api/v1/search fires for empty/1-char input; the recents group renders in the empty state", + "evidence": "the request-absence trace + the recents screenshot" + }, + { + "clause": "RLS parity both personas: the restricted member's global search returns NO hit for a row their RLS hides (INV-1003) while the identical admin query returns it — the /search path rides the same read scope as /data (parity with search.rls-both-personas); UI absence is a courtesy, the server response is the authority", + "oracle": "api", + "verify": "GET /api/v1/search?q=INV-1003 as the member yields no INV-1003 hit and leaks no count of it; as admin it returns INV-1003", + "evidence": "the paired persona responses" + }, + { + "clause": "the /search full page uses the same global-search path and empty-state: SearchResultsPage fires the same searchAll (GET /api/v1/search) and groups record hits by object; a no-hit query renders the designed empty state with no error toast or console error", + "oracle": "network", + "verify": "the page's search request is GET /api/v1/search?q=; the no-hit query yields the empty state, not an error envelope", + "evidence": "the page search trace + the empty-state screenshot + clean console" + } + ], + "negative": [ + "ANY invisible-row hit for the restricted member (or a leaked count of it) is an RLS bypass — P1 FAIL, reproduce twice and file (mirrors search.rls-both-personas)", + "record hits rendered flat with no per-object heading regresses #3371 — a FAIL against the grouped-results contract", + "an empty input firing a search request, or showing stale prior results instead of recents, is a FAIL", + "Enter selecting a hit but not navigating is a FAIL — but rule out the automation-input trap first (ref-targeted select, not a coordinate click) before recording it", + "running the deny side as admin proves nothing (wrong-persona): the RLS check must run as the non-privileged member" + ], + "variants": [ + "open via ⌘K (Cmd+K)", + "open via Ctrl+K", + "open via the header Search button (action:command-palette:open)", + "open via the ?palette=1 / ?cmdk=1 deep-link", + "surface: ⌘K palette", + "surface: /search SearchResultsPage" + ], + "traps": ["stale-console-bundle", "hydration-race", "wrong-persona", "automation-input"], + "source": [ + "objectui packages/app-shell/src/chrome/CommandPalette.tsx (⌘K palette: useRecordSearch, record hits grouped by object #3371, recents empty-state, onSelect navigate to /<object>/record/<id>, 'Open full search' → <baseUrl>/search, overlay data-testid overlay:command-palette)", + "objectui packages/app-shell/src/context/CommandPaletteProvider.tsx (ADR-0054 C1/C3: ⌘K/Ctrl+K accelerator, ?palette=1/?cmdk=1 via useUrlOverlay, idempotent openCommandPalette)", + "objectui packages/app-shell/src/layout/AppHeader.tsx (header Search button data-testid action:command-palette:open / open-mobile → openCommandPalette, ADR-0054 C1)", + "objectui packages/app-shell/src/views/SearchResultsPage.tsx (/apps/:appName/search — same searchAll path, record hits grouped by object + nav matches, designed empty state)", + "objectui packages/react/src/hooks/useRecordSearch.ts (prefers dataSource.searchAll → GET /api/v1/search; per-object find({ $search }) fanout fallback when searchAll is absent)", + "objectui packages/data-objectstack/src/index.ts (searchAll → GET /api/v1/search?q=, returns { query, hits }; 404 → empty when the search plugin is absent)", + "packages/rest/src/rest-route-ledger.ts (GET /api/v1/search, family search, source route-manager, client search) + packages/metadata-protocol/src/protocol.ts (searchAll backing)", + "search.rls-both-personas (the RLS × $search read-path mechanism this item reuses) and search.cross-field-object-search (the /data $search path this item is DISTINCT from — this tests the console global-search UI + the /search route)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "new item: console global-search UI (⌘K palette + header button ADR-0054 C1 + /search page) over GET /api/v1/search — object-grouped hits, RLS parity, Enter-navigates, empty-shows-recents; grounded in objectui app-shell + the framework search route ledger", "ref": "claude/platform-test-checklist-ocwugl" } + ] + } + ] +} diff --git a/docs/qa/platform-checklist/areas/studio-authoring.json b/docs/qa/platform-checklist/areas/studio-authoring.json new file mode 100644 index 0000000000..ad2271da89 --- /dev/null +++ b/docs/qa/platform-checklist/areas/studio-authoring.json @@ -0,0 +1,747 @@ +{ + "$comment": "Standing platform test checklist — area ledger. Hand-edited, append-only; validated by scripts/check-platform-checklist.mjs (pnpm check:platform-checklist). Authoring rules: docs/qa/platform-checklist/README.md · execution protocol: RUNNER.md.", + "area": "studio-authoring", + "title": "Studio authoring — the admin/maker loop: packages, objects, views, record pages, draft→publish", + "items": [ + { + "id": "studio-authoring.first-run-loop", + "title": "The first-run authoring loop closes: package → object → record → app → publish → end-user, zero code, zero restarts", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P0", + "surface": "mixed", + "personas": ["admin (seeded admin@objectos.ai)", "end user (same account driving the published app)"], + "fixtures": { + "app": "showcase", + "requires": [ + "a fresh boot of the showcase example — objectstack dev --ui --seed-admin -p <port> -d file:/tmp/<run>/data.db (the audit's environment: vendored console matching .objectui-sha, seeded admin admin@objectos.ai/admin123)" + ], + "knownGaps": [ + "publish fires in ONE click with no confirmation and the Changes panel lists items without field-level diff (audit finding 3, ADR-0016 §3.6 step 4 open) — do NOT assert a review/confirm step exists; its absence is a logged UX gap, not a run failure", + "a new app scaffolds ZERO nav items (audit finding 6) — the manual Interfaces → Add nav item → bind-to-object wiring in the steps IS the current contract; do not expect the just-built object to be pre-wired", + "mixed-language Studio chrome (audit finding 4) is asserted by i18n.studio-follows-app-locale — locale consistency is that item's business, not this one's" + ] + }, + "steps": [ + "boot the showcase example (objectstack dev --ui --seed-admin -p <port> -d file:/tmp/<run>/data.db), sign in as admin@objectos.ai/admin123, and note the boot timestamp in the server log (the zero-restart clause reads it later)", + "Home → 'Build an app' → Studio landing → create a NEW writable package (维修中心 / com.example.repairs) via the new-package wizard", + "Data pillar → new object 'Repair Ticket' (the identifier auto-suggests repair_ticket from the display name) → add a picklist field Status with 3 values → Save draft", + "open the Changes panel and Publish ('Published all drafts in this package (one atomic release)')", + "create a record in the runtime-faithful Records grid with a Status value chosen", + "Create app 'Repair Center' (identifier auto-suggested) → Interfaces pillar → add a nav item bound to repair_ticket → Publish", + "return Home: the launcher must show Repair Center; open it and drive the end-user list", + "verify server truth after each publish: GET /api/v1/meta/object/repair_ticket, GET /api/v1/data/repair_ticket, GET /api/v1/meta/app?id=<new app id>", + "negative probe: on the READ-ONLY com.example.showcase package, attempt 'New object' (or 'Add field') and drive the save through to the server; capture the server response" + ], + "acceptance": [ + { + "clause": "package create round-trips and the package switcher lists the new writable package without a page reload", + "oracle": "network", + "verify": "the create POST succeeds and the switcher shows the package in the same session (pinned by objectui e2e/live/studio-object-designer.spec.ts test F1)", + "evidence": "the create request/response + switcher screenshot" + }, + { + "clause": "the published object is server-real: the meta read returns repair_ticket with the authored picklist field carrying exactly the 3 authored options — never ticked off the designer repaint", + "oracle": "api", + "verify": "GET /api/v1/meta/object/repair_ticket body contains the Status field with its 3 options", + "evidence": "the meta read" + }, + { + "clause": "the record entered in the Studio Records grid persists to the data plane", + "oracle": "api", + "verify": "GET /api/v1/data/repair_ticket lists the record with the chosen Status value stored under the field's API name", + "evidence": "the data read" + }, + { + "clause": "the published app is live in the Home launcher and the end-user list renders the record with the picklist LABEL chip (label resolution, not the raw value)", + "oracle": "screenshot", + "verify": "launcher screenshot shows Repair Center; end-user list screenshot shows the record row with the label chip; GET /api/v1/meta/app?id=<app id> carries the nav item bound to the object", + "evidence": "the two screenshots + the app meta read" + }, + { + "clause": "the loop closes with ZERO server restarts and zero code — publish alone made the package, object, and app live (the audit's benchmark: 'Zero code, no dead ends, minutes end-to-end')", + "oracle": "log", + "verify": "the server log shows a single boot for the whole loop; no restart was performed between the first step and the end-user list", + "evidence": "server log excerpt spanning the run" + }, + { + "clause": "the writable/read-only gate is enforced SERVER-side: the authoring write into com.example.showcase is rejected by the server even where the client lets the gesture through", + "oracle": "api", + "verify": "the negative probe's server response is a rejection naming the read-only/installed-package cause (audit finding 1 proved this side holds); the client-side lock itself is access-security.readonly-package-locks-studio — cite a pass there, do not re-prove the DOM lock here", + "evidence": "the rejected write's response body" + } + ], + "negative": [ + "silent acceptance of an authoring write into a read-only package is a FAIL (ADR-0057 D10 — the server is the authoritative gate; the client lock is courtesy)", + "a 'published' app absent from the Home launcher, or an end-user list rendering raw picklist values instead of labels, is a FAIL" + ], + "traps": ["stale-console-bundle", "automation-input", "hydration-race"], + "source": [ + "docs/audits/2026-07-studio-package-create-ux-dogfood.md ('The loop closes' — the canonical walk; findings 1/3/4/6 carried as knownGaps)", + "ADR-0016 §9 (the MVP loop this proves)", + "ADR-0057 D10 (server-side gate authority)", + "access-security.readonly-package-locks-studio (client-side lock — cross-referenced, not duplicated)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new area item: the full first-run authoring loop, grounded step-by-step in the 2026-07 Studio dogfood audit ('The loop closes'), with the audit's open UX findings carried as knownGaps instead of asserted", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "studio-authoring.object-designer-roundtrip", + "title": "Object designer round-trip: field add/edit/reorder persists to metadata, identifiers derive and survive typing", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "a writable package to author into (create one via the Studio wizard or POST /api/v1/packages — the source-loaded com.example.showcase is read-only and rejects designer saves server-side)" + ] + }, + "steps": [ + "open the object designer on the writable package: /_console/apps/com.objectstack.studio/metadata/object/new?package=<writable pkg>", + "TYPE the object name repair_asset character by character (never .fill — the F2 per-keystroke-sanitisation bug only reproduces when typed) and confirm the underscore survives", + "add a Text field, set its label to 'Asset Code', blur — the API name input must derive asset_code (F3)", + "add a Picklist field plus one EMPTY option row; wait out the debounced live validation — no validation banner may appear for the empty row (F4); then author 3 real options", + "Save draft, Publish; GET /api/v1/meta/object/repair_asset and record the full field list and its order", + "reopen the designer: rename one picklist value, drag-reorder the fields, Save draft + Publish again", + "GET /api/v1/meta/object/repair_asset again — the rename AND the new field order must be in the persisted metadata, not just the canvas", + "create a record via the Records tab and GET /api/v1/data/repair_asset/<id> — the stored column key must be the derived API name (asset_code)" + ], + "acceptance": [ + { + "clause": "designer edits persist to the metadata store: added, edited, and reordered fields read back from the meta API exactly as authored — the designer repaint is never the oracle", + "oracle": "api", + "verify": "before/after GET /api/v1/meta/object/repair_asset: field set, picklist values, and field ORDER all match the authored state after each publish", + "evidence": "the two meta reads, diffed" + }, + { + "clause": "a new field's API name derives from its label on blur, so the data column is the derived name — never a frozen field_N (the contract behind audit finding 2, where the saved record stored 'field_2': 'in_progress' forever)", + "oracle": "api", + "verify": "the field-apiname input shows asset_code after the label blur (pinned by objectui F3), and the stored record's column key is asset_code", + "evidence": "designer screenshot + the record read" + }, + { + "clause": "typed identifiers survive per-keystroke sanitisation — repair_asset keeps its underscore when typed char-by-char", + "oracle": "dom", + "verify": "after a screenshot confirms the designer rendered, the name input's value equals repair_asset (pinned by objectui F2; pre-fix this yielded 'repairasset')", + "evidence": "screenshot + input value read" + }, + { + "clause": "an empty picklist option row does not trip spec validation (no developer-facing 'System identifier must be at least 2 characters' banner), while the PUBLISHED picklist carries exactly the 3 authored options", + "oracle": "api", + "verify": "no metadata-validation-banner after the debounce (F4's negative assertion), and the meta read shows 3 options — the empty row was dropped, not persisted", + "evidence": "banner-absence screenshot + the meta read" + } + ], + "negative": [ + "an API name frozen at field_N after its label was set pre-save is audit finding 2 regressed — FAIL, but confirm against a fresh objectui build first: the vendored /_console bundle may predate the derive-on-blur fix the e2e now pins" + ], + "traps": ["stale-console-bundle", "automation-input"], + "automated": { "kind": "e2e", "ref": "objectui: e2e/live/studio-object-designer.spec.ts" }, + "source": [ + "objectui: e2e/live/studio-object-designer.spec.ts (F1–F4, the objectui#1926 regression classes: per-keystroke sanitisation, label→api-name derivation, empty picklist row, switcher refresh)", + "docs/audits/2026-07-studio-package-create-ux-dogfood.md (finding 2 — field API name did not follow the label at audit time)", + "docs/audits/2026-07-studio-package-create-ux-dogfood.md (finding 1 — read-only package rejects designer saves server-side, which is why a writable package is a fixture requirement)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: designer→metadata round-trip with the meta API as oracle, pinning the four objectui live-e2e regression classes (F1–F4) and the audit's field_N identifier finding", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "studio-authoring.view-authoring-live", + "title": "List + form view authoring goes live in the running app on publish — no server restart", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["admin (authors)", "end user (consumes the published views)"], + "fixtures": { + "app": "showcase", + "requires": [ + "a runtime-authored object in a writable package to bind views to (e.g. repair_asset from studio-authoring.object-designer-roundtrip), so no shipped showcase view is mutated" + ] + }, + "steps": [ + "author a view CONTAINER draft via the metadata draft door: PUT /api/v1/meta/view/qa_repair_asset_views?mode=draft with { object: 'repair_asset', list: { type: 'grid', columns: [...] }, form: { ... } } — the container's own keys are list/form/listViews/formViews (ViewSchema); capture the receipt", + "as an end user, open the object's list BEFORE publishing: it must still render the previously published views (or the synthesized default) — the draft is staged only", + "publish: POST /api/v1/meta/view/qa_repair_asset_views/publish", + "reload the app page (client reload allowed, NO server restart): the list must render the authored column set; open a record: the form view layout must apply", + "read back through the consumer door: GET /api/v1/meta/view?object=repair_asset (the getViewsByObject read) returns the container", + "edit the list view — reorder the columns and add one — re-publish, reload, and re-screenshot", + "capture the server log across the whole sequence (the no-restart clause reads it)" + ], + "acceptance": [ + { + "clause": "the draft save answers 200 with state:'draft' and a version (the ADR-0008 OCC token) in the receipt", + "oracle": "api", + "verify": "SaveMetaItemResponse fields per packages/spec/src/api/protocol.zod.ts: success, version, seq, state:'draft'", + "evidence": "the PUT response body" + }, + { + "clause": "pre-publish, end-user surfaces still serve the last ACTIVE views — the draft is invisible ('a draft is staged only — it is not served to the runtime until published')", + "oracle": "screenshot", + "verify": "the end-user list rendered before publish shows the OLD columns; pair with a GET of the view name showing no draft leakage", + "evidence": "pre-publish screenshot + meta read" + }, + { + "clause": "post-publish, the list renders the authored columns and the form renders the authored layout with NO server restart — a client page reload is the documented semantics (the audit's whole loop closed live inside one server session)", + "oracle": "screenshot", + "verify": "post-publish screenshots show the new list columns and form layout; the server log shows no restart between draft, publish, and render", + "evidence": "post-publish screenshots + server log excerpt" + }, + { + "clause": "the published container round-trips through the consumer read door", + "oracle": "api", + "verify": "GET /api/v1/meta/view?object=repair_asset includes qa_repair_asset_views with the authored list/form bodies", + "evidence": "the object-scoped view read" + }, + { + "clause": "edit + re-publish updates the live list (new column order renders) — the authoring loop is repeatable, not create-only", + "oracle": "screenshot", + "verify": "the second-publish screenshot shows the reordered/extended columns", + "evidence": "before/after screenshots of the list" + } + ], + "negative": [ + "a view change that needs a SERVER restart to appear is a FAIL against the audit's zero-restart benchmark", + "a draft's columns leaking into the end-user list before publish is a lifecycle FAIL (the full draft→publish contract is studio-authoring.draft-publish-lifecycle — this item only asserts the view-flavored live side)" + ], + "traps": ["stale-console-bundle", "hydration-race"], + "source": [ + "packages/spec/src/ui/view.zod.ts (container keys list/form/listViews/formViews; the 'read by getViewsByObject() / GET /meta/view?object=' binding; guidance map for wrong-layer keys)", + "packages/spec/src/api/protocol.zod.ts (SaveMetaItemResponse: state draft|active, 'staged only — not served until published')", + "docs/audits/2026-07-studio-package-create-ux-dogfood.md (publish→live launcher/list inside one server session)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: list/form view authoring through the ?mode=draft door with live-in-app verification, grounded in ViewSchema's container contract and the audit's zero-restart loop", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "studio-authoring.record-page-roundtrip", + "title": "Record-page authoring round-trip: created bound to its object, seeded from the default layout, block-edited, published, rendered", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["admin (authors)", "end user (opens the record)"], + "fixtures": { + "app": "showcase", + "requires": ["seeded showcase_invoice records to render the published page against (stock showcase seed)"] + }, + "steps": [ + "open /_console/apps/showcase_app/metadata/page/new and fill the create form: Label 'Invoice Page <uniq>' (slugifies into Name), Object showcase_invoice; Save", + "capture the PUT /api/v1/meta/page/<name> request the save issues", + "assert the draft body: type:'record', object:'showcase_invoice', and regions seeded NON-empty from the object's synthesized default detail page (the page resource's createSeed hook) including a record:highlights block — never a blank canvas (ADR-0034 / objectui#1541)", + "open the page editor (/apps/showcase_app/metadata/page/<name>): Basics and Layout sections render, and 'Add block' opens the block-type picker", + "add a block (e.g. Card), Save draft, Publish through the ResourceEditPage draft/publish chrome", + "GET /api/v1/meta/page/<name>: the published regions must include the added block", + "open a showcase_invoice record as an end user: the page assignment (usePageAssignment) renders the authored page over the synthesized default", + "screenshot the editor canvas and the rendered record page" + ], + "acceptance": [ + { + "clause": "the created page persists bound to its object with PRE-SEEDED regions — record:highlights present, regions non-empty", + "oracle": "network", + "verify": "the captured PUT body has type:'record', object:'showcase_invoice', Array.isArray(regions) with blocks including record:highlights (pinned by objectui e2e/live/studio-record-page.spec.ts)", + "evidence": "the captured PUT payload" + }, + { + "clause": "the page editor exposes block authoring: the picker offers schema-backed block kinds (Card, Section, Record details)", + "oracle": "dom", + "verify": "after a screenshot confirms the editor rendered, the Add-block dialog lists the three kinds (pinned by objectui e2e/live/studio-editor.spec.ts)", + "evidence": "screenshot + dialog DOM read" + }, + { + "clause": "draft→publish round-trip: the added block is in the PUBLISHED metadata read back from the server", + "oracle": "api", + "verify": "GET /api/v1/meta/page/<name> after publish contains the added block in regions", + "evidence": "the meta read" + }, + { + "clause": "the published record page renders on a real record for the end user", + "oracle": "screenshot", + "verify": "opening a showcase_invoice record shows the authored page including the added block", + "evidence": "record-page screenshot" + } + ], + "negative": [ + "a new record page opening as a blank canvas (regions: []) is objectui#1541 regressed — the createSeed seeding is the point of the item; FAIL" + ], + "traps": ["stale-console-bundle", "hydration-race", "automation-input"], + "automated": { "kind": "e2e", "ref": "objectui: e2e/live/studio-record-page.spec.ts" }, + "source": [ + "objectui: e2e/live/studio-record-page.spec.ts (#1541, ADR-0034 — create bound + seeded regions, asserted off the PUT payload)", + "objectui: e2e/live/studio-editor.spec.ts (page editor sections + Add-block picker contract)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: record-page authoring round-trip pinned to the two objectui live e2e specs (create-seeded draft, block picker) and extended to the publish + end-user render sides they do not cover", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "studio-authoring.draft-publish-lifecycle", + "title": "Metadata draft→publish lifecycle: drafts staged not served, publish flips visibility atomically, conflicts and invalid drafts refused, and the history/audit/diff/rollback forensics tell the truth", + "since": "v16", + "status": "active", + "revision": 2, + "priority": "P1", + "surface": "mixed", + "personas": ["admin (authors drafts)", "end user (must not see drafts)"], + "fixtures": { + "app": "showcase", + "requires": [ + "scratch metadata names only (e.g. qa_lifecycle_probe) via the draft door PUT /api/v1/meta/<type>/<name>?mode=draft — no shipped showcase file is touched", + "a writable package with two stageable drafts for the package-wide publish-drafts door" + ] + }, + "steps": [ + "author a dashboard draft: PUT /api/v1/meta/dashboard/qa_lifecycle_probe?mode=draft with one dataset-bound widget (dataset: showcase_task_metrics, dimensions: ['status'], values: ['task_count']) — the same door areas/dashboards.json exercises", + "record the receipt: { success, version, seq, state: 'draft' }", + "BOTH sides pre-publish: GET /api/v1/meta/dashboard/qa_lifecycle_probe as the runtime read → 404 (never published) — the draft body must not be served; in Studio the item shows the 'Unpublished draft' badge and the 'Changes · n' counter", + "publish per-ref: POST /api/v1/meta/dashboard/qa_lifecycle_probe/publish → 200; GET now serves the authored body", + "author a SECOND draft revision on the same name and confirm the live read keeps serving revision 1 until that draft is published", + "concurrency guard: re-PUT with the STALE version as If-Match and capture the 409", + "package-wide door: stage two drafts in the writable package and POST /api/v1/packages/<pkg id>/publish-drafts — both flip in one atomic release (the audit's 'Published all drafts in this package (one atomic release)' toast)", + "author an INVALID draft (a widget carrying a stray legacy key) and attempt its publish — the author-time gate must reject the draft→active transition (#4463)", + "meta forensics (after the two publishes of qa_lifecycle_probe — revision 1, then the second revision): GET /api/v1/meta/dashboard/qa_lifecycle_probe/history — the durable sys_metadata_history events must list BOTH published revisions (a dashboard is an overlay type, so history is real; a non-overlay type answers { events: [] } by design)", + "GET /api/v1/meta/dashboard/qa_lifecycle_probe/diff?from=1&to=2 (or omit the params for previous-vs-current) — the structural diff must name the widget key that changed between the revisions, not dump the whole body", + "POST /api/v1/meta/dashboard/qa_lifecycle_probe/rollback with { toVersion: 1 } (send X-Actor or drive it under an authenticated session so the actor is attributable); the runtime GET must then serve revision 1's body again and the rendered dashboard must show revision 1's widget", + "attempt a rollback with a missing/invalid toVersion and capture the 400 INVALID_REQUEST guard", + "GET /api/v1/meta/dashboard/qa_lifecycle_probe/audit — the save/publish/rollback rows must carry the acting user; run every forensics probe against the REST route-manager server os dev serves, NEVER a simulated dispatch (the dispatcher /meta branch swallows /history as a compound name and 404s)" + ], + "acceptance": [ + { + "clause": "a draft save answers state:'draft' and is staged only — the runtime read does not serve it (both sides captured: receipt + 404/last-active read)", + "oracle": "api", + "verify": "PUT receipt has state:'draft' (SaveMetaItemResponseSchema); the follow-up GET returns 404 (or the last ACTIVE body for a previously-published name), never the draft body", + "evidence": "receipt + pre-publish GET" + }, + { + "clause": "publish flips visibility exactly once: the post-publish read serves the authored body; the pre-publish read never did", + "oracle": "api", + "verify": "POST /meta/dashboard/qa_lifecycle_probe/publish → 200, then GET returns { type, name, item } with the widget intact", + "evidence": "publish response + post-publish GET" + }, + { + "clause": "a pending second draft does not perturb the live revision until published", + "oracle": "api", + "verify": "after staging draft 2, GET still returns revision 1's body; after publishing draft 2, GET returns revision 2", + "evidence": "the three reads" + }, + { + "clause": "optimistic concurrency holds: a write carrying a stale If-Match version answers 409 metadata_conflict — a concurrent edit is reported, never silently overwritten", + "oracle": "api", + "verify": "re-PUT with the superseded version token → 409 with the metadata_conflict code (the ADR-0008 chain the receipt's version field exists for)", + "evidence": "the 409 response" + }, + { + "clause": "package-wide publish-drafts promotes every pending draft in one atomic release — and a non-compliant draft (e.g. an object draft missing the package namespace prefix) aborts the batch BEFORE any promotion", + "oracle": "api", + "verify": "POST /api/v1/packages/<id>/publish-drafts flips both staged drafts; the namespace-gate rejection path leaves ALL drafts unpromoted (packages/objectql/src/protocol-publish-package-drafts.test.ts pins the atomicity)", + "evidence": "the publish-drafts response + post-state reads" + }, + { + "clause": "an invalid draft cannot cross into active: the author-time rules gate the draft→active transition (#4463) — publish of the stray-key draft is refused", + "oracle": "api", + "verify": "the publish attempt on the invalid draft errors; the live read still serves nothing (or the prior good revision)", + "evidence": "the refused publish + follow-up GET" + }, + { + "clause": "Studio chrome tells the truth: 'Unpublished draft' badge and 'Changes · n' counter while pending, cleared after publish", + "oracle": "screenshot", + "verify": "badge/counter visible pre-publish, gone post-publish (the audit found this model 'reads consistently everywhere')", + "evidence": "before/after Studio screenshots" + }, + { + "clause": "the durable history lists BOTH published revisions after two publishes — GET /meta/dashboard/qa_lifecycle_probe/history returns the sys_metadata_history events for revision 1 and revision 2 (real events because a dashboard is an overlay type; a non-overlay type returning { events: [] } is by design, not a miss)", + "oracle": "api", + "verify": "the /history body carries two version entries with ascending seq/version — consulted on the REST route-manager server, not the dispatcher", + "evidence": "the history response" + }, + { + "clause": "diff names the changed key, not a whole-body dump — GET .../diff (from=1&to=2, or previous-vs-current) isolates the widget/dimension key that differs between the two revisions", + "oracle": "api", + "verify": "the diff body reports the changed path (the edited widget key), not the entire document", + "evidence": "the diff response" + }, + { + "clause": "rollback restores revision 1 AND the live app serves it — POST .../rollback { toVersion: 1 } → 200, the runtime GET then serves revision 1's body, and the rendered dashboard shows revision 1's widget; a missing/invalid toVersion answers 400 INVALID_REQUEST, never a silent no-op", + "oracle": "api", + "verify": "post-rollback GET /meta/dashboard/qa_lifecycle_probe returns revision 1's body (a screenshot of the reverted widget corroborates the served side); the bad-toVersion attempt returns 400 INVALID_REQUEST", + "evidence": "the rollback response + the post-rollback GET + the reverted-render screenshot + the 400 response" + }, + { + "clause": "audit rows carry the actor — GET .../audit lists the save/publish/rollback attempts (allowed and denied) each stamped with the acting user, resolved from X-Actor / the session identity, never anonymous", + "oracle": "api", + "verify": "the /audit body's rows for this name include the publish and rollback actions with a non-empty actor field", + "evidence": "the audit response" + } + ], + "negative": [ + "a draft body served to end users before publish is the lifecycle FAIL this item exists for", + "a publish that answers 200 while the live read still serves the old body is a FAIL — the ADR-0045 visibility flip failing loudly is exactly the path packages.ts warns about, and silence there is worse than the warning", + "a forensics route consulted on the dispatcher instead of the REST route-manager server is a recording error — the dispatcher /meta branch swallows /history as a compound name and 404s (rest-route-ledger.ts note); the oracle is the live server os dev serves", + "a rollback that answers 200 while the live read still serves the newer revision is a FAIL (the restore must actually flip the served body)" + ], + "traps": ["hydration-race", "dispatcher-vs-hono-route"], + "automated": { "kind": "e2e", "ref": "packages/qa/dogfood/test/dashboard-designer-roundtrip.dogfood.test.ts" }, + "source": [ + "packages/spec/src/api/protocol.zod.ts (SaveMetaItemResponse: state draft|active, version as If-Match/409 OCC token, 'staged only — not served until published')", + "packages/runtime/src/domains/packages.ts (POST /packages/:id/publish-drafts, ADR-0033/ADR-0045 visibility flip + its failure warning)", + "packages/objectql/src/protocol-publish-package-drafts.test.ts (atomic namespace gate; #4463 author-time rules gate the draft→active transition)", + "packages/rest/src/rest-server.ts (GET /meta/:type/:name/{history,audit,diff} + POST .../rollback: overlay-type history vs { events: [] } for non-overlay; rollback body { toVersion }, 400 on a missing/invalid toVersion; actor from X-Actor / session)", + "packages/rest/src/rest-route-ledger.ts (client bindings meta.getHistory / getAudit / diffItem / rollbackItem; the 'dispatcher /meta swallows /history as a compound name and 404s' note — routes hunter #12)", + "docs/audits/2026-07-studio-package-create-ux-dogfood.md ('Unpublished draft' badge, 'Changes · n', 'one atomic release' toast)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: both sides of the draft→publish gate (staged-not-served / flipped-on-publish), plus OCC 409, atomic package-wide publish, and the #4463 invalid-draft publish refusal — grounded in the spec receipt schema, the runtime publish-drafts handler, and the pinned dogfood roundtrip", "ref": "claude/platform-test-checklist-ocwugl" }, + { "revision": 2, "date": "2026-08-08", "change": "clause-extension (routes hunter #12): meta forensics — GET /meta/:type/:name/{history,audit,diff} + POST .../rollback (two publishes → history lists both, diff names the changed key, rollback restores rev-1 and the live app serves it, audit rows carry the actor); traps gain dispatcher-vs-hono-route (the dispatcher /meta branch 404s /history)", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "studio-authoring.authoring-validation-not-persisted", + "title": "An invalid authored shape is rejected at save with a LOCATED error and is not persisted", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": ["scratch metadata names only (qa_invalid_probe / qa_invalid_views) — rejected drafts must leave nothing behind, so no cleanup dependency"] + }, + "steps": [ + "attempt PUT /api/v1/meta/object/qa_invalid_probe?mode=draft with a field MISSING its type; capture the status and full error body", + "attempt PUT /api/v1/meta/view/qa_invalid_views?mode=draft with `type`/`columns` at the CONTAINER level (the flat-list-view mistake); capture the guidance error", + "after each rejection, GET the name back and record what the store holds", + "drive one invalid shape through the Studio designer (browser door) and capture the surfaced error — it must locate the offending field, not say a generic 'save failed'", + "author the CORRECTED shapes (field with a type; views wrapped under list:) and confirm both save 200 — the gate rejects the shape, not the name", + "dashboard-kind stray keys are deep-covered by dashboards.strict-widget-rejects-stray-keys — cite a pass there rather than re-enumerating the 11 legacy keys here" + ], + "acceptance": [ + { + "clause": "an invalid object shape is rejected at save with a located error naming the failing path (the '[invalid_metadata] … fields.<name>.type: Required' shape)", + "oracle": "api", + "verify": "the rejection is 4xx and its body names the exact field path that failed spec validation", + "evidence": "the error body" + }, + { + "clause": "a wrong-layer view container key is rejected with guidance naming where the key belongs ('`type` belongs to a single VIEW, not to the container. Wrap it: defineView({ list: { … } }) …')", + "oracle": "api", + "verify": "the error text for container-level type/columns carries the ViewSchema guidance-map prescription, giving the author the fix", + "evidence": "the error text" + }, + { + "clause": "rejected drafts are NOT persisted — the rejection is authoritative, not cosmetic", + "oracle": "api", + "verify": "GET after each rejected PUT returns 404 (or the last GOOD revision for a pre-existing name), never a body containing the invalid shape", + "evidence": "the GET responses paired with each rejected PUT" + }, + { + "clause": "the browser door surfaces the same rejection visibly — a located error banner/toast, never a silent dead Save", + "oracle": "screenshot", + "verify": "the Studio save attempt shows the validation error naming the offending field", + "evidence": "the error-state screenshot" + }, + { + "clause": "the corrected shapes save 200 — the gate is precise about the shape, not the operation", + "oracle": "api", + "verify": "both corrected PUTs succeed with state:'draft' receipts", + "evidence": "the two success receipts" + } + ], + "negative": [ + "a 2xx on an invalid draft, or a rejection that leaves the invalid body readable afterwards, is a FAIL", + "silent client-side swallowing — no visible error after a failed save — is a FAIL even though the server refused correctly (the author must SEE the located error)" + ], + "traps": ["stale-console-bundle", "automation-input"], + "source": [ + "packages/runtime/src/http-dispatcher.test.ts (the located '[invalid_metadata] object/bad failed spec validation: fields.amount.type: Required' error shape)", + "packages/spec/src/ui/view.zod.ts (container guidance map: type/columns/data/viewKind/filters/sort each name the wrap prescription)", + "dashboards.strict-widget-rejects-stray-keys (dashboard-kind stray keys — cross-referenced, not duplicated)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: authoring validation with located errors and verified non-persistence, sampling object + view kinds and cross-referencing the deepened dashboard stray-key item instead of duplicating it", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "studio-authoring.org-override-registry-gate", + "title": "The metadata type registry gates runtime writes: allowOrgOverride=false kinds refuse overlay (403 not_overridable), allowRuntimeCreate=false kinds refuse creation — both sides", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "api", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "the stock showcase artifact (artifact-backed objects like showcase_task and packaged views are the locked targets; a scratch name serves the allowed-create side)" + ] + }, + "steps": [ + "read the authority first: DEFAULT_METADATA_TYPE_REGISTRY in packages/spec/src/kernel/metadata-plugin.zod.ts declares per-kind allowOrgOverride and allowRuntimeCreate — if the flags have changed, revise this item's variants before running", + "positive overlay: PUT /api/v1/meta/view/<a packaged showcase view name> with a modified label (view: allowOrgOverride true) → expect acceptance; GET the name and confirm the overlay wins at read", + "negative overlay: PUT /api/v1/meta/object/showcase_task (artifact-backed; object: allowOrgOverride false) with any modified body → capture the refusal", + "negative create: PUT /api/v1/meta/job/qa_probe_job (job: allowRuntimeCreate false — a runtime-authored job could never be scheduled, #4509) → capture the refusal", + "positive create: PUT /api/v1/meta/object/qa_org_probe as a brand-NEW object (object: allowRuntimeCreate true) → expect acceptance — the two flags gate different doors", + "cleanup + reset semantics: DELETE /api/v1/meta/view/<overlaid name> and confirm reset:true, then GET returns the artifact default again", + "run every probe against the LIVE server os dev runs — never a simulated dispatch" + ], + "acceptance": [ + { + "clause": "a kind with allowOrgOverride=false refuses the org-level override of an artifact-backed item with 403 not_overridable — the lock is server-side", + "oracle": "api", + "verify": "PUT /api/v1/meta/object/showcase_task answers 403 with the not_overridable code (the runtime behavior the registry's own doc comment declares)", + "evidence": "the 403 response body" + }, + { + "clause": "an overlay-enabled kind accepts the write AND the overlay takes precedence at read — the other side of the same gate", + "oracle": "api", + "verify": "the packaged view accepts the overlay PUT and the follow-up GET serves the overlaid label (overlay-precedence)", + "evidence": "the accepted write + the read showing the overlay" + }, + { + "clause": "a kind with allowRuntimeCreate=false refuses creation with 403 (not_creatable) — declared-but-inert metadata is refused at the door, not stored to never run", + "oracle": "api", + "verify": "PUT /api/v1/meta/job/qa_probe_job answers 403 not_creatable (job's flags exist precisely because a runtime job's handler could never resolve — ADR-0049)", + "evidence": "the 403 response" + }, + { + "clause": "runtime-creatable kinds accept a brand-new item — locked-override and locked-create are independent gates (object: override locked, create open)", + "oracle": "api", + "verify": "the new-name object PUT is accepted while the artifact-backed object PUT was refused, in the same run", + "evidence": "the paired responses" + }, + { + "clause": "deleting the overlay row resets to the artifact default: reset:true when a row was removed, reset:false when none existed", + "oracle": "api", + "verify": "DELETE answers per DeleteMetaItemResponseSchema and the follow-up GET serves the artifact body again", + "evidence": "the delete response + post-delete read" + } + ], + "negative": [ + "a 200 on an overlay write against an artifact-backed object/field is the FAIL this registry exists to prevent (per-org schema drift and upgrade conflicts — the rationale written into the registry entry itself)" + ], + "variants": [ + "object (override locked, create open)", + "field (override locked, create open)", + "job (create locked — 403 not_creatable)", + "view (override open)", + "dashboard (override open)" + ], + "traps": ["dispatcher-vs-hono-route"], + "automated": { "kind": "unit", "ref": "packages/objectql/src/overlay-precedence.test.ts" }, + "source": [ + "packages/spec/src/kernel/metadata-plugin.zod.ts (DEFAULT_METADATA_TYPE_REGISTRY per-kind flags; allowOrgOverride doc: 'runtime returns HTTP 403 not_overridable'; the object/field lock rationale; job's #4509 create lock)", + "packages/objectql/src/overlay-precedence.test.ts ('denied — must throw 403 (not_overridable or not_creatable)')", + "packages/metadata-protocol/src/protocol.ts (isRuntimeCreateAllowed — the write-gate authority)", + "ADR-0005 (metadata customization opt-in), ADR-0049 (enforce-or-remove — the job rationale)" + ], + "history": [ + { "revision": 1, "date": "2026-08-07", "change": "new item: both sides of the registry's runtime-write gates (not_overridable / not_creatable vs accepted overlay / accepted create), variants sampled straight from DEFAULT_METADATA_TYPE_REGISTRY and pinned to the overlay-precedence suite", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "studio-authoring.expression-editors", + "title": "The Studio CEL editors reach the SAME verdict as the engine: formula result-type inference, previous. completion in conditional rules, and RLS lint + test-run — the editor UI, not a second grammar", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "a writable package with an object carrying a formula field (role=value) and a field conditional rule (visibleWhen/readonlyWhen/requiredWhen) — the stock showcase formula shapes are the models to author against (showcase_invoice line_total = record.quantity * record.unit_price, showcase_project remaining-budget, field-zoo); the source-loaded com.example.showcase is read-only, so the SAVE round-trip needs a writable package (the editors' lint/autocomplete/inferred-type/test-run themselves run client-side and can be OBSERVED read-only on the showcase fields)", + "a writable permission set to host the RLS USING/CHECK CEL editor + test-run (PermissionAdvancedFacets)", + "the @objectstack/formula engine present in the console bundle — celAuthoring lazy-loads it" + ], + "knownGaps": [ + "the CEL editors lazy-load @objectstack/formula and FEATURE-DETECT every entry point (celAuthoring.ts) — a missing/older engine degrades to no lint / no suggestions / test-run 'unavailable', never an exception. If the vendored bundle lacks the engine, record the affected clauses blocked(dependency) and verify the graceful-degradation clause instead" + ] + }, + "steps": [ + "ground the engine truth FIRST (rule 6 parity): for each probe expression, capture the canonical verdict from the engine — via MCP validate_expression (site: formula / flow_condition) or `objectstack build` — so the editor is judged against server truth. The ENGINE itself is pinned by ai.mcp-validate-expression / api-backend.formula-gates; this item only checks the EDITOR agrees", + "F1 formula result-type: open the object field designer (ObjectFieldInspector) on a formula field (type=formula, role=value, scope=record), TYPE a proven-number expression (record.est_hours * 1.1, or record.quantity * record.unit_price) and, separately, one cel-js cannot prove (record.a + record.b); after the 250ms debounce, screenshot the inferred-result-type (Σ) affordance for each", + "type an unknown field (record.est_hourz) and a bare field ref in record scope (est_hours, no record. prefix); capture the inline error under the field (aria-invalid, destructive border) and its record.<field> fix text", + "F2 conditional-rule editor: open the same field's visibleWhen editor (scope=record, roots record/previous/parent); type `previous.` and then `record.` and capture the autocomplete listbox — it must offer the object's field catalog; type `current_user.` and confirm it does NOT list this object's fields; accept a suggestion and confirm the insertion", + "F4 RLS editor: open the permission set's Advanced Facets RLS policy (PermissionAdvancedFacets), enter a USING predicate that is NOT a simple field comparison (blast-radius) and one with a parse fault; confirm the non-pushdownable filter raises a WARNING (Save stays enabled) while the parse fault raises a blocking ERROR that disables Save (celErrorCount>0 → title perm.cel.saveBlocked)", + "RLS test-run: open the CelTestRunDialog ('Test this policy against a sample record'), supply a sample record + current_user, Run; capture allow (shield-check) / deny (shield-x) / a non-boolean value (non-bool smell) across three sample+predicate pairs", + "parity cross-check: for the SAME expressions, confirm the editor's verdict tier (error/warning/ok, inferred type, allow/deny) equals the engine verdict captured in step 1", + "persistence round-trip (writable package): Save the edited formula field → GET /api/v1/meta/object/<obj> shows Field.returnType stamped from the inferred type; Save the RLS policy → GET /api/v1/meta/permission/<name> carries the USING/CHECK predicate" + ], + "acceptance": [ + { + "clause": "the formula editor (role=value, scope=record) shows the inferred result type after the debounced lint — 'Number' for a proven-number formula, 'unknown' for record.a + record.b (cel-js cannot prove number vs string) — surfaced as the Σ affordance, not guessed", + "oracle": "screenshot", + "verify": "the inferred-type line reads Number for the proven formula and unknown (with the double()/int() hint) for the unprovable one", + "evidence": "the two inferred-type screenshots" + }, + { + "clause": "the inferred type stamps Field.returnType on save, but ONLY when the author actually edited the formula this session — GET /meta/object/<obj> shows returnType equal to the inferred type; a formula field left un-edited this session keeps its prior returnType (the formulaEdited guard prevents clobbering)", + "oracle": "api", + "verify": "the meta read after saving an edited formula carries returnType = the inferred type; an un-edited sibling formula's returnType is unchanged", + "evidence": "the meta read" + }, + { + "clause": "the editor's lint TIERS equal the canonical engine's — an unknown field and a bare field ref in record scope are ERRORS (with the record.<field> fix, blocking Save), a non-pushdownable USING read filter is a WARNING; the editor calls the SAME @objectstack/formula validateExpression the server and validate_expression use (ADR-0032), so the verdict is identical, not a second grammar", + "oracle": "api", + "verify": "for each probe, the editor's inline finding severity matches validate_expression / build for the same expression+site (error vs warning vs ok)", + "evidence": "the paired editor screenshot + engine verdict, per probe" + }, + { + "clause": "record. / previous. member completion offers the object's field catalog, while suppressed roots (current_user, parent) do NOT offer this object's fields, and a bare field ref in record scope is withheld from bare completion and flagged as an error — because a bare ref silently evaluates to null at runtime", + "oracle": "dom", + "verify": "after a screenshot confirms the editor rendered, the autocomplete listbox after 'previous.' / 'record.' lists field names; after 'current_user.' it does not list this object's fields; a bare 'est_hours' shows the record.<field> error", + "evidence": "the autocomplete DOM reads + the bare-ref error" + }, + { + "clause": "the RLS blast-radius advisory and the Save gate work together: a non-pushdownable USING read filter raises the fail-open WARNING (advisory, Save stays enabled), while a parse fault raises a blocking ERROR that disables Save (celErrorCount>0, title perm.cel.saveBlocked) — a malformed predicate cannot be persisted", + "oracle": "screenshot", + "verify": "the non-pushdownable filter shows the widen-access warning with Save still enabled; a parse-faulted predicate disables Save", + "evidence": "the two editor states" + }, + { + "clause": "test-run dry-runs through the SAME engine the server uses: a predicate that should allow the sample returns allow, one that should deny returns deny, and a non-boolean returns the value/non-bool smell — matching a direct engine evaluate of the same predicate+scope", + "oracle": "screenshot", + "verify": "allow / deny / value outcomes for three sample+predicate pairs, each matching the engine's own verdict", + "evidence": "the three outcome banners" + }, + { + "clause": "editor↔engine parity is the whole point (rule 6): for every probe the editor's verdict equals the engine verdict from ai.mcp-validate-expression / api-backend.formula-gates — this item does NOT re-prove the engine, it proves the EDITOR reaches the identical verdict rather than maintaining a second grammar (ADR-0032)", + "oracle": "api", + "verify": "the per-probe editor verdicts equal the engine verdicts captured up front; cite a pass of ai.mcp-validate-expression for the engine side", + "evidence": "the parity table" + }, + { + "clause": "when @objectstack/formula cannot load the editors degrade to no-lint / no-suggestions / test-run 'unavailable', never an exception that breaks the form (feature-detect + swallow)", + "oracle": "test", + "verify": "run objectui packages/app-shell/src/views/metadata-admin/celAuthoring.test.ts (unavailable-engine cases) and cite its output — do not hand-break the bundle in the browser", + "evidence": "the test output" + } + ], + "negative": [ + "an editor verdict that DIFFERS from the engine (validate_expression / build) for the same expression is a FAIL — a second grammar in the GUI is exactly what celAuthoring exists to prevent (ADR-0032)", + "a parse-fault formula or RLS predicate that Saves anyway (Save not gated on celErrorCount) is a FAIL — a malformed RLS predicate silently mis-scopes rows and some evaluation paths FAIL OPEN, widening access with no error", + "record. / previous. completion offering an unbound root's members, or a bare field ref not flagged in record scope, is a FAIL — it authors a predicate that silently never fires / evaluates to null" + ], + "variants": [ + "formula field expression (role=value, scope=record) — inferred result type", + "field conditional rule visibleWhen / readonlyWhen / requiredWhen (scope=record, roots record/previous/parent) — previous. completion", + "RLS USING read filter (pushdown / fail-open advisory)", + "RLS CHECK write filter" + ], + "traps": ["stale-console-bundle", "hydration-race", "automation-input"], + "automated": { "kind": "unit", "ref": "objectui: packages/app-shell/src/views/metadata-admin/celAuthoring.test.ts (+ CelPredicateField.test.tsx, CelTestRunDialog.test.tsx, PermissionAdvancedFacets.cel.test.tsx)" }, + "source": [ + "objectui: packages/app-shell/src/views/metadata-admin/celAuthoring.ts (the bridge to @objectstack/formula — the SAME parser/validator the server and the validate_expression agent tool use, ADR-0032; lintCelPredicate / introspectCelScope / testRunCelPredicate / inferCelValueType; lazy feature-detected, error-swallowing degradation)", + "objectui: packages/app-shell/src/views/metadata-admin/CelPredicateField.tsx (inline lint, as-you-type autocomplete incl. record./previous. member completion via FIELD_MEMBER_ROOTS, role=value inferred-result-type affordance, aria-invalid on parse error)", + "objectui: packages/app-shell/src/views/metadata-admin/CelTestRunDialog.tsx (USING/CHECK dry-run allow/deny/value/unavailable through the server's own engine)", + "objectui: packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx (formula role=value scope=record → Field.returnType stamped from the inferred type ONLY once edited; FIELD_RULE_ROOTS record/previous/parent for conditional rules)", + "objectui: packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx (RLS facet hosts CelPredicateField + CelTestRunDialog; onCelErrorsChange → celErrorCount gates Save)", + "ai.mcp-validate-expression + api-backend.formula-gates (the ENGINE parity — cross-referenced; this item only proves the editor agrees)", + "objectui#2413 (RLS CEL editor + test-run), objectui#1582 (conditional-rule/formula scope + inferred type), ADR-0032 (one CEL engine across GUI/SDK/CLI)", + "examples/app-showcase/src/data/objects/invoice.object.ts + project.object.ts + field-zoo.object.ts (stock formula-field expressions to author against)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "new item: the Studio CEL editor UI (formula result-type inference, previous. completion in conditional rules, RLS lint + test-run) round-tripping to the SAME @objectstack/formula verdicts the engine gives — grounded in celAuthoring.ts + CelPredicateField/CelTestRunDialog and cross-referencing the engine-parity items rather than re-proving them", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "studio-authoring.permission-matrix-editor-ux", + "title": "Permission-matrix editor UX: field-filter + bulk apply to EXACTLY the visible fields, and the Bulk column never clips at narrow widths", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": ["admin"], + "fixtures": { + "app": "showcase", + "requires": [ + "a writable permission set to edit in the Permission Matrix editor (env-scope metadata-admin, or a package permission set in the Studio Access pillar) — a read-only source-loaded set can be opened to verify the read-only gate but not to exercise bulk writes", + "an object with MORE THAN 6 fields to expand in the matrix, so the field sub-table's filter renders (FieldsSubTable shows the filter only when fields.length > 6) — a stock showcase object with a wide field set (showcase_account / showcase_invoice)", + "a narrow viewport for the Bulk-column clip check (B3)" + ] + }, + "steps": [ + "open the Permission Matrix editor on a writable permission set (Studio Access pillar, or /_console/apps/com.objectstack.studio/metadata/permission/<name>); expand an object with > 6 fields so the field sub-table's filter renders (threshold is > 6)", + "B4 field filter: type a filter that narrows the field set to a couple of fields; confirm the sub-table's 'n / N' counter and the DOM show only the matching fields (the hidden fields' read/edit checkboxes are absent)", + "B4 bulk-over-visible: click 'Write all' (perm.field.bulk.writable) → it must grant read+write to EXACTLY the visible fields; clear the filter and confirm the previously-hidden fields are UNTOUCHED (still at their prior grant), never bulk-applied", + "on a different filtered set click 'Read all' → it revokes write on the visible fields only; click 'Clear' → it drops the explicit overrides so the visible fields fall back to the read-only default (readable, not editable)", + "Save; GET /api/v1/meta/permission/<name> (or the package draft read) and assert the fields map (keyed `${object}.${field}`) carries overrides for ONLY the fields that were visible when each bulk fired — never a filter-hidden field", + "B3 clip: shrink the viewport to a narrow width; confirm the object matrix's enclosing overflow-auto container SCROLLS horizontally (the table carries min-w-[960px]) and the Bulk column (Read / CRUD / All / None per row) stays reachable, not clipped off the right edge", + "read-only gate: open a read-only package/type set; confirm Save is hidden and the bulk buttons + checkboxes are disabled (the writable gate), and the read-only badge names package-vs-type as the reason", + "the grant→access flip (a flipped verb actually changing a persona's API access) is access-security.permission-matrix-edit-loop — cite a pass there; this item does NOT re-prove that a saved grant changes access" + ], + "acceptance": [ + { + "clause": "the field filter narrows the sub-table to the matching fields only — the visible/total counter and the DOM both reflect the filtered set (hidden fields' read/edit checkboxes absent)", + "oracle": "dom", + "verify": "after a screenshot confirms the sub-table rendered, only the filtered field rows are present and the 'n / N' counter matches the filtered count", + "evidence": "the filtered sub-table DOM read + screenshot" + }, + { + "clause": "a field bulk (Write all / Read all) applies to EXACTLY the visible/filtered fields — after the bulk, clearing the filter shows the previously-hidden fields still at their prior grant, so the wrong-scope write never happened (objectui#2600 B4)", + "oracle": "dom", + "verify": "filter → bulk → clear filter: the hidden fields' read/edit checkbox states are unchanged from before the bulk", + "evidence": "before/after checkbox states across the filter clear" + }, + { + "clause": "server truth proves the scope: the SAVED permission set's fields map (keyed `${object}.${field}`) carries overrides for ONLY the fields visible at bulk time — a filter-hidden field must not appear from a bulk it was never part of", + "oracle": "api", + "verify": "GET /meta/permission/<name>: the fields-map keys touched by a bulk are a subset of the field names visible when that bulk fired", + "evidence": "the meta read" + }, + { + "clause": "'Clear' drops the explicit overrides for the visible fields so they fall back to the default (readable, not editable) — the saved fields map no longer carries those keys, rather than writing an explicit default row", + "oracle": "api", + "verify": "after Clear + Save, the cleared fields' keys are absent from the fields map", + "evidence": "the meta read" + }, + { + "clause": "at a narrow viewport the object matrix container scrolls horizontally and the Bulk column (Read / CRUD / All / None) stays reachable — the min-w-[960px] table forces a scrollbar instead of clipping the last column off the right edge (objectui#2600 B3)", + "oracle": "screenshot", + "verify": "a narrow-width screenshot shows a horizontal scrollbar and, after scrolling right, the full Bulk column", + "evidence": "the narrow-viewport screenshot(s)" + }, + { + "clause": "the read-only gate holds: a read-only package/type hides Save and disables every bulk button + checkbox; the read-only badge names the reason (package vs type)", + "oracle": "screenshot", + "verify": "on the read-only set Save is absent, bulk buttons are disabled, and the badge text distinguishes the package gate (engine.studio.pkg.readonly) from the type gate (perm.readOnly)", + "evidence": "the read-only screenshot" + } + ], + "negative": [ + "a field bulk that writes grants to fields hidden by the filter is the FAIL this item exists for — a wrong bulk scope silently grants or revokes access on fields the admin never saw (assert against the SAVED fields map, not the repaint)", + "the Bulk column clipped off the right edge at a narrow width with no horizontal scroll is the B3 FAIL — the admin cannot reach Read / CRUD / All / None" + ], + "variants": [ + "field bulk: Read all (readable)", + "field bulk: Write all (writable)", + "field bulk: Clear" + ], + "traps": ["stale-console-bundle", "hydration-race", "automation-input"], + "automated": { "kind": "unit", "ref": "objectui: packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.fieldBulk.test.tsx (+ PermissionMatrixEditor.readonly.test.tsx)" }, + "source": [ + "objectui: packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx (FieldsSubTable B4 — field filter + bulkSetFields over visibleNames; PermissionTable min-w-[960px] B3 anti-clip on the Bulk column; the writable gate + celErrorCount Save gate)", + "objectui: packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.fieldBulk.test.tsx ('the filter narrows the field set and bulk acts only on what is visible'; 'Clear drops the overrides so fields fall back to the read-only default')", + "objectui: packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.readonly.test.tsx (the read-only package/type gate)", + "#3358 §5 B3/B4 (permission-matrix editor UX: Bulk column clip; field-filter + bulk over the visible set)", + "access-security.permission-matrix-edit-loop (the grant→access flip — cross-referenced, not duplicated)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "new item: permission-matrix editor UX — field-filter + bulk apply scoped to exactly the visible fields (wrong scope writes wrong grants) and the Bulk column's anti-clip min-width, grounded in PermissionMatrixEditor.tsx + its fieldBulk/readonly tests; cross-refs the permission-matrix-edit-loop for the grant→access side", "ref": "claude/platform-test-checklist-ocwugl" } + ] + } + ] +} diff --git a/docs/qa/platform-checklist/coverage.json b/docs/qa/platform-checklist/coverage.json new file mode 100644 index 0000000000..efa72f6d98 --- /dev/null +++ b/docs/qa/platform-checklist/coverage.json @@ -0,0 +1,183 @@ +{ + "$comment": "Capability-coverage ratchet for the platform test checklist. The universe of governed metadata kinds is DERIVED at check time from packages/spec/liveness/*.json (the ADR-0049 ledger set) — this file must map EVERY kind to at least one checklist item, or waive it with a reason. scripts/check-platform-checklist.mjs enforces both directions: an unmapped kind fails CI (the platform grew a capability the checklist doesn't test), and an entry for a kind with no liveness ledger fails too (orphan, mirrors the liveness ORPHAN discipline). Pattern copied from examples/app-showcase/src/coverage.ts (demonstrated-or-waived, ADR-0060 house ledger style).", + "metadataKinds": { + "action": { + "items": [ + "records-forms.action-param-widgets", + "ai.mcp-run-action-exposure-gate", + "records-forms.action-location-matrix" + ] + }, + "agent": { + "items": [ + "ai.agent-tool-skill-metadata-roundtrip", + "ai.open-edition-honest-degradation" + ] + }, + "api": { + "items": [ + "api-backend.declarative-endpoint-execution" + ] + }, + "app": { + "items": [ + "platform-core.boot-health", + "platform-core.nav-surfaces-render" + ] + }, + "book": { + "waived": "docs-shaped content kind (display-only, ADR-0033 exemption class). The console DOES ship a docs/book reader (objectui apps/console DocPage/BookPage) — but the kind is authored-content-as-data with no independent runtime behavior to gate beyond serving; parse coverage exists via the spec's own tests. Waived for behavior, not for lack of a surface." + }, + "dashboard": { + "items": [ + "dashboards.strict-widget-rejects-stray-keys", + "dashboards.chart-type-matrix", + "dashboards.global-filters-rescope", + "dashboards.chart-first-paint", + "dashboards.empty-null-bucket-boundaries" + ] + }, + "dataset": { + "items": [ + "dashboards.dataset-report-authoring" + ] + }, + "datasource": { + "items": [ + "integration-system.external-datasource-federated-read" + ] + }, + "doc": { + "waived": "docs-shaped content kind (display-only, ADR-0033 exemption class) — same posture as `book`: a reader surface exists in the console, but there is no runtime behavior to assert beyond serving." + }, + "email_template": { + "items": [ + "integration-system.email-template-render" + ] + }, + "field": { + "items": [ + "records-forms.field-type-matrix", + "records-forms.conditional-rules-header", + "records-forms.cascading-options", + "records-forms.lookup-picker-create-new" + ] + }, + "flow": { + "items": [ + "automation.flow-node-type-matrix", + "automation.trigger-type-matrix", + "automation.flow-error-handling", + "automation.screen-flow-roundtrip", + "automation.durable-suspend-restart", + "automation.flow-runs-page-test-trigger", + "automation.flow-toggle-kill-switch" + ] + }, + "hook": { + "waived": "record hooks are exercised INDIRECTLY by every CRUD/roll-up/attachment item (their side effects are those items' oracles), but no item asserts hook execution itself; needs a fixture hook with an observable, attributable side effect. Candidate follow-up item: hook fires on insert/update/delete with ctx.user populated (PD#9 organizationId contract)." + }, + "job": { + "items": [ + "integration-system.job-scheduled-run" + ] + }, + "mapping": { + "items": [ + "records-forms.named-import-mapping" + ] + }, + "object": { + "items": [ + "records-forms.crud-roundtrip", + "platform-core.metadata-authoring-roundtrip", + "studio-authoring.object-designer-roundtrip" + ] + }, + "page": { + "items": [ + "studio-authoring.record-page-roundtrip", + "platform-core.nav-surfaces-render" + ] + }, + "permission": { + "items": [ + "access-security.crud-permission-matrix", + "access-security.owd-sharing-matrix", + "access-security.fls-mask-and-strip", + "access-security.rls-both-sides", + "access-security.sharing-rules-widen", + "access-security.record-share-grant-revoke", + "access-security.permission-matrix-edit-loop", + "access-security.sharing-rule-authoring-ui", + "access-security.owd-save-gate", + "access-security.share-link-capability-tokens" + ] + }, + "position": { + "items": [ + "access-security.scope-depth-asymmetry", + "approvals.per-group-signoff", + "approvals.dynamic-approver-routing", + "identity-auth.teams-bu-membership" + ] + }, + "query": { + "items": [ + "api-backend.query-contract-matrix" + ] + }, + "report": { + "items": [ + "dashboards.dataset-report-authoring", + "dashboards.drill-through-range", + "dashboards.saved-report-ownership" + ] + }, + "seed": { + "items": [ + "platform-core.seed-integrity" + ] + }, + "skill": { + "items": [ + "ai.agent-tool-skill-metadata-roundtrip", + "ai.skill-instructions-mcp-prompts" + ] + }, + "tool": { + "items": [ + "ai.agent-tool-skill-metadata-roundtrip" + ] + }, + "translation": { + "items": [ + "i18n.surface-matrix", + "i18n.strict-translation-key-rejection", + "i18n.build-gates-hold" + ] + }, + "validation": { + "items": [ + "access-security.write-path-guards", + "records-forms.conditional-rules-header", + "records-forms.validation-rule-type-matrix" + ] + }, + "view": { + "items": [ + "records-forms.view-type-gallery", + "records-forms.form-view-gallery", + "records-forms.list-view-capabilities", + "studio-authoring.view-authoring-live", + "records-forms.gantt-interactions", + "records-forms.saved-view-management" + ] + }, + "webhook": { + "items": [ + "integration-system.webhook-lifecycle" + ] + } + } +} \ No newline at end of file diff --git a/scripts/check-platform-checklist.mjs b/scripts/check-platform-checklist.mjs index 677271d38b..3e9a310823 100644 --- a/scripts/check-platform-checklist.mjs +++ b/scripts/check-platform-checklist.mjs @@ -153,6 +153,71 @@ for (const { file, item } of allItems) { } } +// ── Capability-coverage ratchet ───────────────────────────────────────────── +// "凡是有的能力, 都要测试" made mechanical: the universe of governed metadata +// kinds is derived from packages/spec/liveness/*.json (the ADR-0049 ledger +// set), and coverage.json must map every kind to ≥1 checklist item or waive it +// with a reason. Bidirectional, mirroring the liveness ledger's own +// UNCLASSIFIED/ORPHAN discipline: an unmapped kind fails (the platform grew a +// capability the checklist doesn't test), and a mapped kind with no liveness +// ledger fails (the entry outlived the capability). +const COVERAGE_FILE = join(ROOT, 'docs/qa/platform-checklist/coverage.json'); +const LIVENESS_DIR = join(ROOT, 'packages/spec/liveness'); +let waivedCount = 0; +let mappedCount = 0; +if (!existsSync(COVERAGE_FILE)) { + err('coverage.json', null, 'missing — every liveness-governed metadata kind must be mapped or waived'); +} else if (!existsSync(LIVENESS_DIR)) { + err('coverage.json', null, `cannot derive the kind universe: ${LIVENESS_DIR} not found`); +} else { + let cov; + try { + cov = JSON.parse(readFileSync(COVERAGE_FILE, 'utf8')); + } catch (e) { + cov = null; + err('coverage.json', null, `does not parse as JSON: ${e.message}`); + } + if (cov) { + const universe = readdirSync(LIVENESS_DIR) + .filter((f) => f.endsWith('.json')) + .map((f) => basename(f, '.json')) + .sort(); + const map = cov.metadataKinds ?? {}; + for (const kind of universe) { + const entry = map[kind]; + if (entry === undefined) { + err('coverage.json', kind, 'UNCLASSIFIED — the platform has this capability (liveness ledger exists) but the checklist neither tests nor waives it. Add items or a waiver with a reason.'); + continue; + } + const hasItems = Array.isArray(entry.items) && entry.items.length > 0; + const hasWaiver = typeof entry.waived === 'string' && entry.waived.trim().length > 0; + if (hasItems === hasWaiver) { + err('coverage.json', kind, 'must have EITHER non-empty "items" OR a non-empty "waived" reason — not both, not neither'); + continue; + } + if (hasItems) { + mappedCount++; + for (const id of entry.items) { + if (!allIds.has(id)) err('coverage.json', kind, `maps to unknown item id "${id}"`); + else { + const mapped = allItems.find((r) => r.item.id === id); + if (mapped?.item.status === 'retired') { + err('coverage.json', kind, `maps to retired item "${id}" — point at its successor or re-waive the kind`); + } + } + } + } else { + waivedCount++; + } + } + for (const kind of Object.keys(map)) { + if (!universe.includes(kind)) { + err('coverage.json', kind, `ORPHAN — mapped kind has no packages/spec/liveness/${kind}.json ledger; remove the entry or restore the ledger`); + } + } + } +} + if (errors.length) { console.error(`check-platform-checklist: ${errors.length} problem(s)\n`); for (const e of errors) console.error(` ✗ ${e}`); @@ -162,4 +227,4 @@ if (errors.length) { const total = allItems.length; const active = allItems.filter(({ item }) => item.status === 'active').length; -console.log(`check-platform-checklist: OK — ${files.length} areas, ${total} items (${active} active).`); +console.log(`check-platform-checklist: OK — ${files.length} areas, ${total} items (${active} active); coverage: ${mappedCount} kinds mapped, ${waivedCount} waived.`); From 5d50fc01da72e5f279b48a6f27d0c8db33bf77ad Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Sat, 8 Aug 2026 04:12:23 +0000 Subject: [PATCH 07/14] =?UTF-8?q?feat(qa):=20=E5=8F=98=E4=BD=93=E6=96=B0?= =?UTF-8?q?=E9=B2=9C=E5=BA=A6=E9=97=A8=20+=20hook/=E9=A1=B5=E9=9D=A2/?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E4=B8=8B=E9=92=BB=20+=20sweep=20=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E9=85=8D=E6=96=B9(177=20=E9=A1=B9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automation to keep the checklist current with minimal human effort, plus a deep-drill round on the surfaces a maintainer asked about (objects, field types, hooks, custom pages, cascading fields). Automation: - scripts/check-platform-checklist.mjs gains a VARIANTS-FRESHNESS ratchet: a matrix item pins the spec enum it was authored against via `enumSource` {file, export, expect}; the validator extracts the enum's current member count from spec source and fails CI when it drifts, with a precise fix instruction. Closes the gap the kind-level coverage ratchet left — the latter catches a NEW metadata kind, enumSource catches a NEW value inside an existing kind's enum. Pinned: field types (49), chart types (20), action locations (7), webhook triggers (5), flow node types (20). - SWEEP.md — the five-angle gap-hunt as a committed runbook, so "跑一轮 coverage sweep" is an AI-executable refresh (the human-effort half); the deterministic gates are the zero-human half. Deep-drill items (the 4th stale waiver corrected → coverage 26 mapped / 2 waived, only book/doc display-only remain): - records-forms.object-hook-lifecycle (un-waives `hook` — showcase ships 4 observable hooks): 6 lifecycle events, CEL previous-vs-record transition semantics (#4770/#4784), async+retry, onError abort-rollback vs log, priority. - studio-authoring custom-page items: declarative block composition + data binding, page variables + action interpolation, html-parsed vs react-executed source tiers with the OS_PAGE_REACT capability gate. - records-forms field drills: per-type write-path constraint enforcement (22 variants, exact ADR-0114 codes), multilevel cascade clear-semantics, encrypted field fail-closed. Two server-side integrity gaps found → FOLLOW-UPS D7/D8. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YD9f6FYyMraUWYeJf53V43 --- docs/qa/platform-checklist/FOLLOW-UPS.md | 2 + docs/qa/platform-checklist/README.md | 58 +- docs/qa/platform-checklist/SWEEP.md | 63 +++ .../platform-checklist/areas/automation.json | 312 +++++++++-- .../platform-checklist/areas/dashboards.json | 15 +- .../areas/integration-system.json | 15 +- .../areas/records-forms.json | 530 +++++++++++++++++- .../areas/studio-authoring.json | 282 ++++++++++ docs/qa/platform-checklist/coverage.json | 4 +- scripts/check-platform-checklist.mjs | 62 ++ 10 files changed, 1273 insertions(+), 70 deletions(-) create mode 100644 docs/qa/platform-checklist/SWEEP.md diff --git a/docs/qa/platform-checklist/FOLLOW-UPS.md b/docs/qa/platform-checklist/FOLLOW-UPS.md index 91cd6e51d8..79d962004e 100644 --- a/docs/qa/platform-checklist/FOLLOW-UPS.md +++ b/docs/qa/platform-checklist/FOLLOW-UPS.md @@ -21,6 +21,8 @@ gaps. Security-sensitive ones were deliberately NOT filed publicly — your call | D4 | **`SharedViewLink` builds dead `/share/<object>/<view>?token=` URLs** — client-generated token, no matching console route (only `/s/:token`), no server persistence. Registered but unused. | objectui plugin-view/src/SharedViewLink.tsx | — (not an item; demo-grade) | low — file a cleanup issue | | D5 | **List "Share" button is a no-op** — renders when `schema.sharing` is set but has no onClick. | objectui plugin-list/src/ListView.tsx | — | low — file a cleanup issue | | D6 | **`/api/v1/datasources` admin CRUD has no route ledger** — mounted by serve.ts, absent from rest-route-ledger.ts (tranche-3 discipline gap). | packages/services/service-datasource/src/admin-routes.ts | integration-system.datasource-admin-lifecycle (source note) | low — internal discipline | +| D7 | **Parent-only PATCH does not revalidate a stale dependent child** — `evaluateOptionVisibility` skips fields absent from the payload, so changing only the parent leaves a now-invalid child value in place server-side; integrity rests entirely on the client clear. | packages/objectql/src/validation/rule-validator.ts (`!(name in data) continue`) | records-forms.cascading-multilevel-and-clear (knownGap) | integrity — safe to file | +| D8 | **Lookup cascade scope is existence-only server-side** — `assertReferencesResolve` accepts any EXISTING id regardless of `lookupFilters` scope (a cross-account contact that exists is accepted on direct POST). May be by-design (filters = UI courtesy) — needs a maintainer ruling: declared ≠ enforced, or documented courtesy. | packages/objectql/src/engine.ts (assertReferencesResolve) | records-forms.cascading-multilevel-and-clear (knownGap) | integrity/design — needs ruling | ## 2. Docs promise capabilities the runtime doesn't deliver (PD#10, docs side — file docs issues) diff --git a/docs/qa/platform-checklist/README.md b/docs/qa/platform-checklist/README.md index 2c2f754857..add7613328 100644 --- a/docs/qa/platform-checklist/README.md +++ b/docs/qa/platform-checklist/README.md @@ -108,15 +108,65 @@ demonstrated-or-waived ratchet, applied to testing instead of demonstration. Enumerable surfaces *inside* a capability (49 field types, 20 chart types, flow node types, query operators, decision actions, …) are covered by `variants` matrices on the -items themselves, each derived from the spec's own Zod enums with the source cited — -when the spec grows a variant, the matrix item's next revision must grow with it (and -the showcase `coverage.test.ts` ratchet will already be failing if the variant isn't -demonstrable at all). +items themselves, each derived from the spec's own Zod enums with the source cited. + +**Variants stay fresh automatically.** A matrix item may pin the spec enum it was +authored against with an `enumSource` field: + +```jsonc +"enumSource": { + "file": "packages/spec/src/data/field.zod.ts", // repo-root-relative spec source + "export": "FieldType", // the exported z.enum(...) const + "expect": 49 // member count the variants match +} +``` + +The validator extracts the enum's *current* member count from that source (comment- +stripped, deduped) and fails when it no longer equals `expect`. So when the platform +grows a 50th field type or a 21st chart type, this item's next CI run goes red with a +precise instruction: revise the variants matrix, bump the item revision, set `expect` +to the new count. This closes the automation gap the coverage ratchet alone left — the +kind-level ratchet catches a *new metadata kind*, `enumSource` catches a *new value in +an existing kind's enum* — so "spec grew a variant" becomes a checklist-blocking event +instead of a silent drift the showcase `coverage.test.ts` only catches indirectly. +Items currently pinned: field types, chart types, action locations, webhook triggers, +flow node types. Pin more as matrices are added. A waiver is a debt marker, not an exemption: it names what fixture or surface is missing, so paying it down is a matter of adding the fixture and flipping the entry to `items`. +### Variants freshness — spec enum drift fails CI on the item itself + +Matrix items may pin the spec enum their `variants` were authored against: + +```jsonc +"enumSource": { "file": "packages/spec/src/data/field.zod.ts", "export": "FieldType", "expect": 49 } +``` + +The validator extracts the enum's CURRENT member count from the spec source at check +time (comment-stripped, deduped) and fails with `VARIANTS STALE` when it no longer +equals `expect` — so a PR that adds a 50th field type cannot merge without revising the +matrix (or consciously bumping `expect` with a revision). This closes the loop the +kind-level ratchet leaves open: new *kinds* are caught by the liveness-derived universe, +new *members of an existing kind* are caught by these pins. Enums declared inline +(anonymous `z.enum` inside an object literal) cannot be pinned by export name — those +matrices still rely on the showcase `coverage.test.ts` demonstrability gate. + +### How the checklist keeps itself current (the automation model) + +1. **New capability kind** → a `packages/spec/liveness/<kind>.json` ledger appears → + coverage ratchet fails CI until the kind is mapped or waived. Automatic. +2. **New member of an enumerable surface** → `enumSource` pin fails CI on the matrix + item. Automatic for pinned enums. +3. **New feature inside an existing kind** → process: the feature PR lands a `since: + v<current>` item (same discipline as changesets); the release sweep filter catches + stragglers. +4. **Periodic re-sweep** → [SWEEP.md](./SWEEP.md) is a runbook any AI session can + execute on request ("run a coverage sweep") — five independent gap-hunt angles, + dedupe, author, validate. The 2026-08 sweep it encodes found 3 stale waivers and + ~55 missing items; re-running it is how drift that slips past 1–3 gets caught. + ## How a release sweep works A release no longer gets a hand-written checklist. The sweep for `vN` is a **filter diff --git a/docs/qa/platform-checklist/SWEEP.md b/docs/qa/platform-checklist/SWEEP.md new file mode 100644 index 0000000000..64e0ffe5e3 --- /dev/null +++ b/docs/qa/platform-checklist/SWEEP.md @@ -0,0 +1,63 @@ +# Coverage sweep runbook — for an AI session, on request + +How to re-run the capability-coverage gap hunt that built and audited this checklist. +A human should only need to say **"跑一轮 coverage sweep"** — everything below is +executable by the AI session itself. Expected cadence: before each major release, or +after any large platform surface lands. + +This is the AI-participation half of keeping the checklist current. The other half is +automatic and needs no human at all: `scripts/check-platform-checklist.mjs` (CI, every +PR) fails when a new metadata **kind** is unmapped (coverage ratchet) or a spec **enum** +grows a value a matrix item was pinned against (`enumSource` freshness ratchet). Those +catch drift on the PR that causes it. This sweep catches the harder class — a whole +surface or behavior nobody wrote an item for — which no deterministic gate can find. + +## What a sweep is + +Five READ-ONLY gap-hunter agents, each enumerating the platform from a different angle +and diffing it against the current checklist. Different angles catch different miss +classes — the 2026-08 sweep's finds (4 factually-stale waivers, the untested built-in +apps, sharing rules, the ACTION_LOCATIONS matrix) each came from a different angle a +single reader would not have covered. + +| angle | enumerate from | catches | +|---|---|---| +| 1. Console UI surfaces | objectui packages (app-shell chrome, plugin-*, e2e specs) | interactions covered piecemeal but never as a surface (drag, guards, personalization, buttons) | +| 2. Spec enums | every `z.enum` / union / const array in packages/spec/src + the formula function lib | behavior-bearing enums with no `variants` matrix | +| 3. Routes & runtime | ALL route ledgers (runtime, rest, service-*, auth) + non-ledgered mounts | routes reachable but semantically untested; dispatcher-vs-hono seams (#3361 class) | +| 4. Built-in apps | packages/apps/{setup,studio,account} page by page | admin/user pages nobody walked; settings/session/org surfaces | +| 5. Docs claims | content/docs/capabilities/*.mdx, release plans, showcase tours | promised capabilities with no item; docs advertising retired features (PD#10) | + +## How to run it + +1. **Read the current state first** — every `areas/*.json`, `coverage.json`, and this + dir's README/RUNNER. The gap is only real if nothing already covers it. +2. **Dispatch the five hunters in parallel**, READ-ONLY (they write no files). Each + returns a structured gap table: `surface | evidence path | current coverage (item id + or NONE/partial) | proposed item id | sketch | stock fixture?`. Give each hunter the + list of already-resolved gaps so they don't re-report. +3. **Dedupe** the five reports into one register (the 2026-08 sweep used a + `PENDING-GAPS.md` scratch file, since deleted). Overlap is expected and is signal — + a gap found from three angles is high-priority. +4. **Author** the new items via per-area writer agents, one area file per writer so + they never collide. Every item follows the deep-test contract in README.md; a fixture + that doesn't exist is a `blocked`/`knownGap`, never faked coverage. +5. **Reconcile `coverage.json` centrally** (a single writer): un-waive any kind a hunter + proved has a stock fixture, map new items to their kinds, pin `enumSource` on any new + variants matrix. +6. **Validate** `node scripts/check-platform-checklist.mjs` until green, then land the + run record under `runs/` and surface product defects / docs drift to the maintainer + in `FOLLOW-UPS.md`. + +## Discipline that made the 2026-08 sweep trustworthy + +- **Ground every claim in source before asserting** — hunters cite real file paths; + writers read the cited source before writing a clause. Several briefs I gave the + writers were factually wrong (`referenceFilters` renamed to `lookupFilters`, + crm-workbench is React not declarative, MCP-off is 404 not 501) and the agents + corrected them against source rather than parroting. +- **Stale waivers are the highest-value find.** Four of six coverage waivers turned out + false (api/datasource/mapping/hook all ship stock fixtures). Re-audit every waiver + each sweep — a waiver is a claim that ages. +- **Defects found while grounding go to FOLLOW-UPS.md as expected-fail probes**, not + silent passes; security-sensitive ones are not filed publicly without the maintainer. diff --git a/docs/qa/platform-checklist/areas/automation.json b/docs/qa/platform-checklist/areas/automation.json index f0d5a7b640..fb99c4de36 100644 --- a/docs/qa/platform-checklist/areas/automation.json +++ b/docs/qa/platform-checklist/areas/automation.json @@ -11,10 +11,14 @@ "revision": 2, "priority": "P1", "surface": "mixed", - "personas": ["admin"], + "personas": [ + "admin" + ], "fixtures": { "app": "showcase", - "requires": ["showcase_batch_reminders (examples/app-showcase/src/automation/flows/index.ts BatchRemindersFlow) — an autolaunched loop flow with a `tasks` list input, runnable on demand via the trigger route"] + "requires": [ + "showcase_batch_reminders (examples/app-showcase/src/automation/flows/index.ts BatchRemindersFlow) — an autolaunched loop flow with a `tasks` list input, runnable on demand via the trigger route" + ] }, "steps": [ "boot showcase isolated (dogfood §0); sign in as the dev admin", @@ -60,7 +64,10 @@ "negative": [ "a flat rendering in which all steps show success is a FAIL of the nesting clause — every step passing is exactly what makes the missing tree easy to tick past; the developer Flow Runs page renders steps flat and must never be cited as this item's oracle" ], - "traps": ["wrong-panel", "hydration-race"], + "traps": [ + "wrong-panel", + "hydration-race" + ], "source": [ "#3358 §2 — 'the developer Flow Runs page renders steps flat; looking there alone reads as a miss'", "packages/spec/src/automation/execution.zod.ts (ExecutionStepLogSchema parentNodeId/iteration/regionKind)", @@ -68,8 +75,18 @@ "examples/app-showcase/src/automation/flows/index.ts (BatchRemindersFlow)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358, encoding its wrong-panel lesson as a trap", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from #3358, encoding its wrong-panel lesson as a trap", + "ref": "#3358" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -80,11 +97,17 @@ "revision": 2, "priority": "P2", "surface": "mixed", - "personas": ["admin"], + "personas": [ + "admin" + ], "fixtures": { "app": "showcase", - "requires": ["showcase_task_due_reminder (type schedule, start config.timeRelative {object: showcase_task, dateField: due_date, offsetDays: [3,1], filter: {status: {$ne: 'done'}}}, runAs system)"], - "knownGaps": ["the stock sweep cadence is the timeRelative default — daily 08:00 UTC — so an in-session fire needs either a cadence override (config.schedule cron/interval on a writable copy of the flow) or a manually provoked sweep; the run record must state which method was used"] + "requires": [ + "showcase_task_due_reminder (type schedule, start config.timeRelative {object: showcase_task, dateField: due_date, offsetDays: [3,1], filter: {status: {$ne: 'done'}}}, runAs system)" + ], + "knownGaps": [ + "the stock sweep cadence is the timeRelative default — daily 08:00 UTC — so an in-session fire needs either a cadence override (config.schedule cron/interval on a writable copy of the flow) or a manually provoked sweep; the run record must state which method was used" + ] }, "steps": [ "boot showcase isolated (dogfood §0); sign in as the dev admin", @@ -133,7 +156,10 @@ "a sweep that also fires for the filter-excluded or out-of-window record is a FAIL even though the matching record fired correctly", "a both-windowing-modes flow accepted silently is a FAIL — the schema declares exactly one mode" ], - "traps": ["seed-data-thin", "stale-dist"], + "traps": [ + "seed-data-thin", + "stale-dist" + ], "source": [ "#3358 §2", "packages/spec/src/automation/time-relative-trigger.zod.ts", @@ -141,8 +167,18 @@ "packages/triggers/trigger-schedule/src/time-relative-trigger.ts" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from #3358", + "ref": "#3358" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -153,11 +189,17 @@ "revision": 2, "priority": "P1", "surface": "mixed", - "personas": ["admin"], + "personas": [ + "admin" + ], "fixtures": { "app": "showcase", - "requires": ["a parent with several filtered roll-ups in different filter shapes (showcase_expense_report: equality / boolean / operator / unfiltered)"], - "knownGaps": ["the visual filter-editor half needs a summary field in a WRITABLE package — the showcase ships read-only, so Studio editing is not reachable on stock fixtures (#3358)"] + "requires": [ + "a parent with several filtered roll-ups in different filter shapes (showcase_expense_report: equality / boolean / operator / unfiltered)" + ], + "knownGaps": [ + "the visual filter-editor half needs a summary field in a WRITABLE package — the showcase ships read-only, so Studio editing is not reachable on stock fixtures (#3358)" + ] }, "steps": [ "boot showcase isolated (dogfood §0); sign in as the dev admin", @@ -203,15 +245,30 @@ "negative": [ "an untouched-filter roll-up that moves on an edit outside its filter (e.g. rejected_count changing on the billable flip) is a FAIL even if every touched value is right" ], - "blocked": { "by": "fixture", "ref": "#3358 (editor half needs a writable-package summary field fixture)" }, - "traps": ["seed-data-thin"], + "blocked": { + "by": "fixture", + "ref": "#3358 (editor half needs a writable-package summary field fixture)" + }, + "traps": [ + "seed-data-thin" + ], "source": [ "#3358 §2 (recompute half proven; editor half explicitly left unticked)", "examples/app-showcase/src/data/objects/expense-report.object.ts (the six roll-up shapes and their filters)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; clause 1 runnable today, clause 2 carries the fixture blocker", "ref": "#3358" }, - { "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial import from #3358; clause 1 runnable today, clause 2 carries the fixture blocker", + "ref": "#3358" + }, + { + "revision": 2, + "date": "2026-08-07", + "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -219,10 +276,12 @@ "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": 1, + "revision": 2, "priority": "P1", "surface": "mixed", - "personas": ["admin"], + "personas": [ + "admin" + ], "fixtures": { "app": "showcase", "requires": [ @@ -233,10 +292,27 @@ ] }, "variants": [ - "start", "end", "decision", "assignment", "loop", - "create_record", "update_record", "delete_record", "get_record", - "http", "notify", "script", "screen", "wait", "subflow", "map", - "connector_action", "parallel", "try_catch", "approval", "approval_revise" + "start", + "end", + "decision", + "assignment", + "loop", + "create_record", + "update_record", + "delete_record", + "get_record", + "http", + "notify", + "script", + "screen", + "wait", + "subflow", + "map", + "connector_action", + "parallel", + "try_catch", + "approval", + "approval_revise" ], "steps": [ "boot showcase isolated (dogfood §0); sign in as the dev admin", @@ -284,8 +360,15 @@ "negative": [ "registering a flow with an unregistered node type (e.g. type 'bogus_node') must be REFUSED at registerFlow / POST /api/v1/automation — the type is validated against the live action registry (ADR-0018), and silent acceptance of an inert node is the #1887 failure shape" ], - "traps": ["wrong-panel", "seed-data-thin", "automation-input"], - "automated": { "kind": "e2e", "ref": "packages/qa/dogfood/test/flow-node.dogfood.test.ts" }, + "traps": [ + "wrong-panel", + "seed-data-thin", + "automation-input" + ], + "automated": { + "kind": "e2e", + "ref": "packages/qa/dogfood/test/flow-node.dogfood.test.ts" + }, "source": [ "packages/spec/src/automation/flow.zod.ts (FlowNodeAction — the built-in seed set; type validated at registerFlow, not by a closed enum)", "packages/spec/src/automation/control-flow.zod.ts (LOOP_NODE_TYPE / PARALLEL_NODE_TYPE / TRY_CATCH_NODE_TYPE, ADR-0031)", @@ -295,8 +378,24 @@ "packages/runtime/src/route-ledger.ts (GET /automation/actions, POST /automation/:name/trigger, GET /automation/:name/runs)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — node-type matrix derived from FlowNodeAction + ADR-0031 containers + plugin-approvals registry types, mapped onto the seeded showcase flows", "ref": "claude/platform-test-checklist-ocwugl" } - ] + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — node-type matrix derived from FlowNodeAction + ADR-0031 containers + plugin-approvals registry types, mapped onto the seeded showcase flows", + "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 2, + "date": "2026-08-08", + "change": "pinned enumSource for the variants-freshness ratchet — spec enum drift now fails CI on this item directly", + "ref": "claude/platform-test-checklist-ocwugl" + } + ], + "enumSource": { + "file": "packages/spec/src/automation/flow.zod.ts", + "export": "FlowNodeAction", + "expect": 20 + } }, { "id": "automation.trigger-type-matrix", @@ -306,14 +405,23 @@ "revision": 1, "priority": "P1", "surface": "mixed", - "personas": ["admin"], + "personas": [ + "admin" + ], "fixtures": { "app": "showcase", "requires": [ "one seeded flow per trigger kind: record_change=showcase_urgent_task_alert (record-after-write, the only type:'record_change' flow) · autolaunched=showcase_batch_reminders (trigger route) · schedule=showcase_scheduled_digest (60s interval) · screen=showcase_reassign_wizard (Tasks row action showcase_bulk_reassign) · api=showcase_inbound_task_webhook (HMAC intake hook) · time_relative=showcase_task_due_reminder (schedule flow hosting config.timeRelative)" ] }, - "variants": ["record_change", "autolaunched", "schedule", "screen", "api", "time_relative"], + "variants": [ + "record_change", + "autolaunched", + "schedule", + "screen", + "api", + "time_relative" + ], "steps": [ "boot showcase isolated (dogfood §0); sign in as the dev admin", "record_change (create leg): POST /api/v1/data/showcase_task with priority='urgent' — showcase_urgent_task_alert fires via record-after-write with previous == null (#3427)", @@ -367,8 +475,14 @@ "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)", "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": ["seed-data-thin", "dispatcher-vs-hono-route"], - "automated": { "kind": "test", "ref": "packages/qa/dogfood/test/flow-trigger-conformance.test.ts" }, + "traps": [ + "seed-data-thin", + "dispatcher-vs-hono-route" + ], + "automated": { + "kind": "test", + "ref": "packages/qa/dogfood/test/flow-trigger-conformance.test.ts" + }, "source": [ "packages/spec/src/automation/flow.zod.ts (Flow.type enum: autolaunched|record_change|schedule|screen|api)", "packages/qa/dogfood/test/flow-trigger-conformance.ledger.ts (one enforced row per type, each with its runtime + proof)", @@ -377,7 +491,12 @@ "packages/spec/src/automation/execution.zod.ts (ExecutionLogSchema.trigger)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — trigger matrix derived from the Flow.type enum and its ADR-0060 D5 conformance ledger; time_relative added as the declarative sweep the schedule runtime hosts", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — trigger matrix derived from the Flow.type enum and its ADR-0060 D5 conformance ledger; time_relative added as the declarative sweep the schedule runtime hosts", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -388,7 +507,9 @@ "revision": 1, "priority": "P1", "surface": "mixed", - "personas": ["admin"], + "personas": [ + "admin" + ], "fixtures": { "app": "showcase", "requires": [ @@ -448,7 +569,10 @@ "an unhandled node failure that leaves its run status=completed, or leaves the run-level error empty, is a FAIL — a dead outbound call reporting success is the inert-automation failure shape (#1887)", "a catch region that runs when the try did NOT fail is a FAIL of the container semantics — check the catch steps are absent from a successful run" ], - "traps": ["wrong-panel", "single-datapoint"], + "traps": [ + "wrong-panel", + "single-datapoint" + ], "source": [ "examples/app-showcase/src/automation/flows/index.ts (ResilientSyncFlow, ADR-0031 try/catch/retry; canonical retry keys #4661)", "packages/spec/src/automation/control-flow.zod.ts (TryCatchConfigSchema)", @@ -457,7 +581,12 @@ "packages/runtime/src/route-ledger.ts (POST /automation — automation.create)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — splits handled (try_catch) vs unhandled failure into one contrast item with API + panel + log oracles", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — splits handled (try_catch) vs unhandled failure into one contrast item with API + panel + log oracles", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -468,10 +597,14 @@ "revision": 2, "priority": "P1", "surface": "browser", - "personas": ["admin"], + "personas": [ + "admin" + ], "fixtures": { "app": "showcase", - "requires": ["the Tasks list row action showcase_bulk_reassign wired to showcase_reassign_wizard (type screen, runAs user; screen node 'collect' with required field new_assignee; downstream update_record)"] + "requires": [ + "the Tasks list row action showcase_bulk_reassign wired to showcase_reassign_wizard (type screen, runAs user; screen node 'collect' with required field new_assignee; downstream update_record)" + ] }, "steps": [ "boot showcase isolated (dogfood §0); sign in as the dev admin; open /apps/showcase_app/showcase_task", @@ -534,8 +667,14 @@ "a resume with the required new_assignee absent that answers 2xx or completes the run is a FAIL — the screen contract's required flag must be enforced server-side, not only by the dialog", "a cancel that leaves the run status=cancelled/completed, deletes the paused row, or silently fires the downstream update_record is a FAIL — dismissing the dialog must not consume the durable suspension (the run must stay resumable); equally, a cancel that makes the run unresumable (a later valid resume 404s 'no suspended run') is a FAIL" ], - "traps": ["automation-input", "hydration-race"], - "automated": { "kind": "e2e", "ref": "objectui e2e/live/screen-flow.spec.ts" }, + "traps": [ + "automation-input", + "hydration-race" + ], + "automated": { + "kind": "e2e", + "ref": "objectui e2e/live/screen-flow.spec.ts" + }, "source": [ "objectui e2e/live/screen-flow.spec.ts (framework#3528 — the trigger → dialog → resume → refresh seam)", "objectui packages/app-shell/src/views/FlowRunner.tsx (onClose = dismiss without POSTing resume; the durable suspension is untouched, so the paused run stays resumable)", @@ -544,8 +683,18 @@ "packages/runtime/src/route-ledger.ts (resume + getScreen routes)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — promotes the objectui live e2e's seam to a ledger item and adds the server-side required-input negative", "ref": "claude/platform-test-checklist-ocwugl" }, - { "revision": 2, "date": "2026-08-08", "change": "added the FlowRunner cancel-mid-flow clause — dismissing the screen dialog parks the run (still paused, resumable) rather than orphaning/cancelling it; API run state is the oracle", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — promotes the objectui live e2e's seam to a ledger item and adds the server-side required-input negative", + "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 2, + "date": "2026-08-08", + "change": "added the FlowRunner cancel-mid-flow clause — dismissing the screen dialog parks the run (still paused, resumable) rather than orphaning/cancelling it; API run state is the oracle", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -556,7 +705,9 @@ "revision": 1, "priority": "P1", "surface": "api", - "personas": ["admin"], + "personas": [ + "admin" + ], "fixtures": { "app": "showcase", "requires": [ @@ -610,14 +761,25 @@ "negative": [ "a paused run that vanishes after restart — or a resume that reports success while the paused row still reads paused — is a FAIL against durability; both were real (#4420: every persist failed into an unread warn while the pause reported success)" ], - "traps": ["stale-dist", "seed-data-thin"], - "automated": { "kind": "e2e", "ref": "packages/qa/dogfood/test/flow-durable-suspend.dogfood.test.ts" }, + "traps": [ + "stale-dist", + "seed-data-thin" + ], + "automated": { + "kind": "e2e", + "ref": "packages/qa/dogfood/test/flow-durable-suspend.dogfood.test.ts" + }, "source": [ "packages/qa/dogfood/test/flow-durable-suspend.dogfood.test.ts (#4470 · #4420 · #4518)", "examples/app-showcase/src/automation/flows/index.ts (TaskFollowUpFlow, ProjectClosureFlow/ClosureSignoffSubflow — nested durable pause, linked-runs model)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — promotes the #4470 durable-suspend proof to a ledger item and adds the nested linked-run pair", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — promotes the #4470 durable-suspend proof to a ledger item and adds the nested linked-run pair", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -628,7 +790,9 @@ "revision": 1, "priority": "P2", "surface": "mixed", - "personas": ["admin"], + "personas": [ + "admin" + ], "fixtures": { "app": "showcase", "requires": [ @@ -639,7 +803,12 @@ "slack DELIVERY is not assertable on stock fixtures — TaskCompletedSlackFlow points at a placeholder channel with no real bot token, so the slack variant is registry-enumeration only" ] }, - "variants": ["rest (plugin)", "slack (plugin, enumeration only)", "showcase_status_api (declarative rest)", "showcase_mcp_tools (declarative MCP)"], + "variants": [ + "rest (plugin)", + "slack (plugin, enumeration only)", + "showcase_status_api (declarative rest)", + "showcase_mcp_tools (declarative MCP)" + ], "steps": [ "boot showcase isolated (dogfood §0); sign in as the dev admin", "GET /api/v1/automation/connectors — record the descriptor list and each connector's action list (the MCP instance's actions must come from its tools/list handshake)", @@ -678,7 +847,10 @@ "negative": [ "a connector_action naming an unregistered connectorId must FAIL its step with a named 'connector … not registered'-class error — a silent no-op success is a FAIL (the retired logger-backed stubs delivered nothing while reporting success, #4343)" ], - "traps": ["stale-console-bundle", "single-datapoint"], + "traps": [ + "stale-console-bundle", + "single-datapoint" + ], "source": [ "examples/app-showcase/src/automation/flows/index.ts (TaskCompletedRestPingFlow, ShowcaseDeclarativeConnectorPingFlow, ShowcaseMcpConnectorEchoFlow)", "examples/app-showcase/objectstack.config.ts (ConnectorRestPlugin/ConnectorSlackPlugin/ConnectorMcpPlugin + declarative connectors)", @@ -686,7 +858,12 @@ "ADR-0097 (provider-bound declarative connector instances)" ], "history": [ - { "revision": 1, "date": "2026-08-07", "change": "initial — connector dispatch matrix over the plugin / declarative-rest / declarative-MCP kinds the showcase seeds, with the unregistered-id negative", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-07", + "change": "initial — connector dispatch matrix over the plugin / declarative-rest / declarative-MCP kinds the showcase seeds, with the unregistered-id negative", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -697,7 +874,9 @@ "revision": 1, "priority": "P2", "surface": "mixed", - "personas": ["admin"], + "personas": [ + "admin" + ], "fixtures": { "app": "showcase", "requires": [ @@ -753,7 +932,11 @@ "a screen-flow test run that dumps the {status:'paused'} JSON envelope and stops — no dialog, no way to finish it — leaving a paused run stranded is a FAIL: that is the framework#3528 regression this page's FlowRunner hand-off exists to prevent", "citing the Studio flow-designer Runs preview (metadata-admin FlowRunsPanel) as this item's surface is a wrong-panel FAIL — this item is the developer:flow-runs PAGE (trigger + inline history), which renders steps FLAT; the nested step-tree lives in automation.flow-run-step-nesting" ], - "traps": ["wrong-panel", "hydration-race", "automation-input"], + "traps": [ + "wrong-panel", + "hydration-race", + "automation-input" + ], "source": [ "objectui apps/console/src/pages/developer/FlowRunsPage.tsx (FlowTestRunner + inline FlowRunsPanel; framework#3528 — hands a paused screen run to FlowRunner so it no longer orphans a paused row)", "objectui packages/app-shell/src/views/FlowRunner.tsx (the shared screen runner reused here)", @@ -762,7 +945,12 @@ "examples/app-showcase/src/automation/flows/index.ts (BatchRemindersFlow tasks input, ReassignWizardFlow screen)" ], "history": [ - { "revision": 1, "date": "2026-08-08", "change": "initial — pins the developer Flow Runs page's typed-input trigger + the framework#3528 screen-flow hand-off (paused run driven to completion, no orphaned row); distinct from flow-run-step-nesting which reads the designer's Runs preview", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — pins the developer Flow Runs page's typed-input trigger + the framework#3528 screen-flow hand-off (paused run driven to completion, no orphaned row); distinct from flow-run-step-nesting which reads the designer's Runs preview", + "ref": "claude/platform-test-checklist-ocwugl" + } ] }, { @@ -773,7 +961,9 @@ "revision": 1, "priority": "P2", "surface": "api", - "personas": ["admin"], + "personas": [ + "admin" + ], "fixtures": { "app": "showcase", "requires": [ @@ -822,15 +1012,23 @@ "a toggle that answers 2xx while /_status still reports enabled/bound unchanged is a FAIL — the reported state must match the enforced state", "toggling an unknown flow that answers 2xx (rather than not-found) is a FAIL" ], - "traps": ["seed-data-thin", "dispatcher-vs-hono-route"], + "traps": [ + "seed-data-thin", + "dispatcher-vs-hono-route" + ], "source": [ "packages/runtime/src/route-ledger.ts (POST /automation/:name/toggle → automation.toggle; GET /automation/_status → automation.getRuntimeStatus)", "packages/services/service-automation/src/engine.ts (toggleFlow → flowEnabled + activateFlowTrigger/deactivateFlowTrigger; getFlowRuntimeStates enabled/bound)", "examples/app-showcase/src/automation/flows/index.ts (UrgentTaskAlertFlow — the record_change flow, #3427)" ], "history": [ - { "revision": 1, "date": "2026-08-08", "change": "initial — pins the /toggle runtime kill switch on a record_change flow: OFF unbinds the trigger so the firing mutation produces no run, ON restores it; runs list + runtime state as oracles", "ref": "claude/platform-test-checklist-ocwugl" } + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — pins the /toggle runtime kill switch on a record_change flow: OFF unbinds the trigger so the firing mutation produces no run, ON restores it; runs list + runtime state as oracles", + "ref": "claude/platform-test-checklist-ocwugl" + } ] } ] -} +} \ No newline at end of file diff --git a/docs/qa/platform-checklist/areas/dashboards.json b/docs/qa/platform-checklist/areas/dashboards.json index 3f417ed2ca..f087552a36 100644 --- a/docs/qa/platform-checklist/areas/dashboards.json +++ b/docs/qa/platform-checklist/areas/dashboards.json @@ -279,7 +279,7 @@ "title": "Every ChartTypeSchema member renders a real chart with the correct marks and series count", "since": "v15", "status": "active", - "revision": 2, + "revision": 3, "priority": "P1", "surface": "browser", "personas": [ @@ -417,8 +417,19 @@ "date": "2026-08-08", "change": "added the period-over-period compareTo: { kind: 'previousPeriod' } clause + scratch-draft probe + parse pin (dashboard-compareto.test.ts #5011)", "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 3, + "date": "2026-08-08", + "change": "pinned enumSource for the variants-freshness ratchet — spec enum drift now fails CI on this item directly", + "ref": "claude/platform-test-checklist-ocwugl" } - ] + ], + "enumSource": { + "file": "packages/spec/src/ui/chart.zod.ts", + "export": "ChartTypeSchema", + "expect": 20 + } }, { "id": "dashboards.dataset-report-authoring", diff --git a/docs/qa/platform-checklist/areas/integration-system.json b/docs/qa/platform-checklist/areas/integration-system.json index 44c6c1cd99..4636bef600 100644 --- a/docs/qa/platform-checklist/areas/integration-system.json +++ b/docs/qa/platform-checklist/areas/integration-system.json @@ -499,7 +499,7 @@ "title": "Outbound webhooks materialize (spec object→object_name, isActive→active), fire per trigger variant through the sys_http_delivery outbox with HMAC + timeout honored, reject retired trigger kinds, and never clobber admin edits", "since": "v15", "status": "active", - "revision": 2, + "revision": 3, "priority": "P1", "surface": "mixed", "fixtures": { @@ -600,8 +600,19 @@ "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-08-08", + "change": "pinned enumSource for the variants-freshness ratchet — spec enum drift now fails CI on this item directly", + "ref": "claude/platform-test-checklist-ocwugl" } - ] + ], + "enumSource": { + "file": "packages/spec/src/automation/webhook.zod.ts", + "export": "WebhookTriggerType", + "expect": 5 + } }, { "id": "integration-system.job-scheduled-run", diff --git a/docs/qa/platform-checklist/areas/records-forms.json b/docs/qa/platform-checklist/areas/records-forms.json index f51f2b04d7..9418e1cc89 100644 --- a/docs/qa/platform-checklist/areas/records-forms.json +++ b/docs/qa/platform-checklist/areas/records-forms.json @@ -127,7 +127,7 @@ "title": "Field-type matrix: every FieldTypeSchema member renders its widget, accepts a valid value, round-trips over HTTP, and rejects invalid input", "since": "v15", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "mixed", "personas": [ @@ -276,8 +276,19 @@ "date": "2026-08-07", "change": "initial — deep-test variant matrix over FieldTypeSchema, pinned to the dogfood HTTP round-trip suite", "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 2, + "date": "2026-08-08", + "change": "pinned enumSource for the variants-freshness ratchet — spec enum drift now fails CI on this item directly", + "ref": "claude/platform-test-checklist-ocwugl" } - ] + ], + "enumSource": { + "file": "packages/spec/src/data/field.zod.ts", + "export": "FieldType", + "expect": 49 + } }, { "id": "records-forms.list-view-capabilities", @@ -1351,7 +1362,7 @@ "title": "Action buttons surface at exactly their declared locations — list toolbar, list row, record header/more/related/section, global nav — and each dispatches for real", "since": "v15", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "browser", "personas": [ @@ -1441,8 +1452,19 @@ "date": "2026-08-07", "change": "initial — closes the button-placement gap: list toolbar / list row / bulk bar / detail-page buttons were covered piecemeal but never as the ACTION_LOCATIONS matrix", "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 2, + "date": "2026-08-08", + "change": "pinned enumSource for the variants-freshness ratchet — spec enum drift now fails CI on this item directly", + "ref": "claude/platform-test-checklist-ocwugl" } - ] + ], + "enumSource": { + "file": "packages/spec/src/ui/action.zod.ts", + "export": "ACTION_LOCATIONS", + "expect": 7 + } }, { "id": "records-forms.validation-rule-type-matrix", @@ -2668,6 +2690,506 @@ "ref": "claude/platform-test-checklist-ocwugl" } ] + }, + { + "id": "records-forms.object-hook-lifecycle", + "title": "Object lifecycle hooks fire on the write path with the right timing, gate, async and error semantics — driven over /api/v1/data/*, oracled by the record effect and the log line", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "the four seeded showcase hooks (examples/app-showcase/src/data/hooks/index.ts, bound via defineStack({ hooks: allHooks })): showcase_normalize_task_title (showcase_task, events ['beforeInsert','beforeUpdate'], trims title, priority 50, onError:'abort'); showcase_stamp_inquiry_defaults (showcase_inquiry, beforeInsert, stamps status='new'/source='web', onError:'abort'); showcase_audit_task_completion (showcase_task, afterUpdate, condition previous.done!=true && record.done==true, async, retryPolicy {maxRetries:3,backoffMs:1000}, capabilities:['log'], onError:'log', priority 90); showcase_warn_over_budget (showcase_project, afterUpdate, condition record.spent!=null && record.budget!=null && record.spent>record.budget, async, capabilities:['log'], onError:'log')", + "showcase_task (title required text + done boolean, defaultValue false), showcase_inquiry (status select whose 'new' option is default:true + source plain text with NO default), showcase_project (budget/spent currency + the spent_within_budget script rule that REJECTS spent > budget*1.2)", + "seeded rows: not-done task 'Build homepage' (done:false) for the transition PATCH; already-done tasks 'Audit current IA' and 'App wireframes' (done:true) for the non-transition proof; projects 'Data Platform' (budget 600000 / spent 420000) and 'Website Relaunch' (budget 150000 / spent 60000)", + "server log capture — the async audit/warn bodies call ctx.log.info/warn, routed to the engine logger (packages/runtime/src/sandbox/body-runner.ts:321 log: engineCtx.logger); AND the ability to register a scratch hook (throwing / ordered) for the variants the four fixtures cannot observe", + "a real running server: hooks fire in the ObjectQL engine on the live /api/v1/data/* route, never on a simulated dispatch" + ], + "knownGaps": [ + "afterInsert and afterDelete have NO fixture hook — the four seeded hooks cover beforeInsert, beforeUpdate and afterUpdate only. Exercise these two events with a scratch log hook or record the variant knownGap; do not fake coverage.", + "beforeDelete has NO seeded showcase hook — the abort-blocks-delete contract is pinned only by the dogfood hef_ref_guard fixture (packages/qa/dogfood/test/hook-error-format.dogfood.test.ts), a test-only stack, not the showcase app; drive beforeDelete via a scratch hook or cite that pin.", + "priority ordering (lowest-first on the SAME object+event) is not fixture-covered: no two showcase hooks share an object+event (normalize is showcase_task/before*, audit is showcase_task/afterUpdate — different events), so ordering needs a scratch pair of hooks on one object+event with distinct priorities.", + "onError:'abort' ROLLBACK and onError:'log' SUPPRESSION are only observable with a hook that THROWS — the four fixtures never throw (their trim/stamp/log bodies always succeed). The abort-rollback, log-tolerate and fail-closed-condition clauses each require a scratch throwing hook (or the cited dogfood abort pin).", + "the async audit/warn lines land AFTER the HTTP write resolves (fire-and-forget runs once the engine has moved on) — a log read taken before the async body executes shows no line; settle before judging any no-fire." + ] + }, + "variants": [ + "beforeInsert — FIXTURE: showcase_stamp_inquiry_defaults (stamps status/source on a new inquiry) and showcase_normalize_task_title (also trims on insert)", + "beforeUpdate — FIXTURE: showcase_normalize_task_title (trims title on every task update)", + "afterUpdate — FIXTURE: showcase_audit_task_completion (task done-transition audit) and showcase_warn_over_budget (project over-budget warn)", + "afterInsert — SCRATCH/knownGap: no showcase hook targets afterInsert; add a scratch afterInsert log hook or record knownGap", + "beforeDelete — SCRATCH/knownGap: no seeded showcase hook; the abort-blocks-delete shape is pinned only by the dogfood hef_ref_guard fixture (hook-error-format.dogfood.test.ts)", + "afterDelete — knownGap: no showcase hook and no dogfood pin; add a scratch afterDelete log hook to exercise", + "multi-event targeting — FIXTURE: showcase_normalize_task_title lists events ['beforeInsert','beforeUpdate'] — ONE hook fires on two write kinds (hook-binder registers per-event × per-object)", + "CEL condition — two-root transition semantics (#4770/#4784) — FIXTURE: showcase_audit_task_completion `previous.done != true && record.done == true`; since #4770 `record` is the record's STATE (stored ⊕ payload, total over declared fields), so `record.done == true` alone is true on every edit of an already-done row — `previous` is what makes 'just became done' expressible", + "CEL condition — total-record `!= null` guard, NOT has() (#4770) — FIXTURE: showcase_warn_over_budget `record.spent != null && record.budget != null && ...`; because `record` is made total over declared fields, `has(record.spent)` is uniformly TRUE even when the field holds null, so only `!= null` keeps `null > null` (which CEL has no overload for) from aborting the expression — an abort #4775 would turn into a REJECTED write", + "async fire-and-forget (after* ONLY) — FIXTURE: showcase_audit_task_completion + showcase_warn_over_budget (async:true); the wrapper ignores async on before* events (hook-wrappers.ts: fireAndForget = Boolean(meta.async) && isAfterEvent)", + "retryPolicy {maxRetries,backoffMs} — FIXTURE declares it: showcase_audit_task_completion {maxRetries:3, backoffMs:1000}; observing an actual RETRY needs a transiently-failing scratch body", + "capabilities:['log'] (L2 sandbox log capability) — FIXTURE: audit + warn bodies call ctx.log.info/warn wired to the engine logger (body-runner.ts:321)", + "onError:'abort' (rollback) — FIXTURE declares it on showcase_normalize_task_title / showcase_stamp_inquiry_defaults; observing the ROLLBACK needs a THROWING before* scratch hook (the fixtures never throw)", + "onError:'log' (tolerate + continue) — FIXTURE declares it on showcase_audit_task_completion / showcase_warn_over_budget; observing the SUPPRESSION needs a throwing async scratch (an async failure never rolls the triggering write back regardless)", + "priority ordering (lowest-first, same object+event) — SCRATCH/knownGap: no two showcase hooks share object+event, so register two scratch hooks on one object+event with priorities e.g. 10 and 90 and read the execution order from the log", + "condition fails CLOSED (#4775) — SCRATCH: an unevaluable/uncompilable condition ABORTS the operation and is NOT softened by onError:'log' nor fire-and-forgotten (the gate runs OUTSIDE both); add a scratch hook with a broken condition (unit-pinned in hook-wrappers.ts)" + ], + "steps": [ + "boot showcase isolated; sign in as seeded admin; confirm the four hooks actually registered before asserting any no-fire — GET /api/v1/meta/types/hook (or the boot log) lists showcase_normalize_task_title / showcase_stamp_inquiry_defaults / showcase_audit_task_completion / showcase_warn_over_budget (a hook that never bound fakes every no-fire — seed-data-thin)", + "stamp defaults (beforeInsert): POST /api/v1/data/showcase_inquiry {name:'os-qa-<runid>'} OMITTING status AND source; re-read via GET and confirm source == 'web' — the hook is the SOLE producer of that value (the source field carries no default), so a correct read proves the hook fired; status == 'new' corroborates but is NOT hook-attributable alone (the 'new' status option is default:true)", + "title trim (beforeInsert + beforeUpdate): POST /api/v1/data/showcase_task with title ' os-qa-<runid> ' (leading/trailing spaces) and valid required fields; re-read → title == 'os-qa-<runid>' (insert trim); then PATCH the same row's title to another padded value and re-read → trimmed again (update trim); ONE multi-event hook covered both", + "completion audit — FIRES (transition): PATCH /api/v1/data/showcase_task/<Build homepage id> {done:true} (previous done:false); after the HTTP response resolves, settle briefly (the audit is async fire-and-forget, it runs after the write returns), then read the server log for 'task completed: Build homepage'", + "completion audit — DOES NOT FIRE (two-root proof): PATCH an already-done task (<App wireframes id>, done:true) changing ONLY priority (medium→high), leaving done untouched; settle; confirm NO new 'task completed: App wireframes' line — previous.done == true makes the transition condition false even though record.done == true", + "over-budget warn — FIRES: PATCH /api/v1/data/showcase_project/<Data Platform id> {spent:650000} (budget 600000 → over budget, but ≤ 720000 so the spent_within_budget rule still permits the write); settle; read the log for 'project over budget: Data Platform (650000 / 600000)'", + "over-budget warn — condition gate (under budget): PATCH 'Website Relaunch' {spent:70000} (still under its 150000 budget); settle; confirm NO warn line (record.spent > record.budget is false) — the same partial write proves `record` merges the stored budget it never sent", + "abort rollback (scratch): register a scratch beforeInsert hook whose body throws, onError:'abort', on a scratch object (or showcase_task); POST a row; capture the refusal and confirm a filtered GET count stays 0 (the write rolled back, no orphan) — cross-check the dogfood hef_ref_guard beforeDelete pin", + "onError:'log' tolerate (scratch): register a scratch async afterUpdate hook whose body throws, onError:'log'; trigger it with a real update; confirm the triggering write STILL landed (re-read shows the change) and the server logged '[hook] async handler error (fire-and-forget)' — a suppressed failure, not a rollback", + "priority ordering (scratch): register two scratch hooks on ONE object+event with priorities 10 and 90, each logging its own name; issue one write; confirm the log order is the priority-10 hook THEN the priority-90 hook (lower runs first)" + ], + "acceptance": [ + { + "clause": "beforeInsert stamps server-controlled defaults: an inquiry POSTed with NO source reads back source == 'web' — the hook is the sole producer of that value (the field has no default), so a correct read PROVES the hook ran; status == 'new' corroborates but is not hook-attributable alone (the 'new' option is default:true)", + "oracle": "api", + "verify": "POST /api/v1/data/showcase_inquiry omitting source/status → GET the created row shows source 'web' (and status 'new'); pinned for the anonymous public-form path by packages/qa/dogfood/test/showcase-public-form.dogfood.test.ts", + "evidence": "the POST payload + the re-read JSON" + }, + { + "clause": "one multi-event hook trims on BOTH write kinds: a padded title ' X ' reads back 'X' after the create (beforeInsert) and again after an update (beforeUpdate) — events:['beforeInsert','beforeUpdate'] on a single hook", + "oracle": "api", + "verify": "re-reads after the padded POST and after the padded PATCH both return the trimmed title", + "evidence": "the two re-reads + the two write payloads" + }, + { + "clause": "the afterUpdate audit fires on the COMPLETING update: PATCH done:false→true emits 'task completed: <title>' through the ['log'] capability — the async, retry-policied, priority-90 hook", + "oracle": "log", + "verify": "after settling for the fire-and-forget async body, the server log carries the line naming the task just completed", + "evidence": "the log excerpt + the triggering PATCH" + }, + { + "clause": "the audit does NOT fire on a NON-transition edit of an already-done task — previous.done != true is false though record.done == true; this is the two-root transition semantics (#4770/#4784), and a fire here would prove the condition collapsed to a bare `record.done == true` state test", + "oracle": "log", + "verify": "PATCH only priority on a done task; a before/after diff of the server log shows NO new 'task completed' line for it", + "evidence": "the before/after log diff around the non-transition PATCH" + }, + { + "clause": "the afterUpdate over-budget warn fires when record.spent > record.budget: PATCH spent above budget (but ≤ 120%, so the spent_within_budget rule permits the write) logs 'project over budget: <name> (<spent> / <budget>)'", + "oracle": "log", + "verify": "the server log carries the warn naming the project and the two numbers after the over-budget PATCH settles", + "evidence": "the log excerpt + the PATCH" + }, + { + "clause": "the condition's `!= null` guard proves `record` is stored ⊕ payload (total over declared fields), NOT the bare patch: an over-budget PATCH that touches ONLY spent still reads budget from the stored row and fires; an under-budget PATCH emits no warn", + "oracle": "log", + "verify": "over-budget spent-only PATCH → warn line; under-budget PATCH → no line; the fire from a partial write is the proof budget was merged from storage", + "evidence": "both log states keyed to the two PATCHes" + }, + { + "clause": "onError:'abort' on a THROWING before* hook rolls the write back: the POST is refused and a filtered GET count stays 0 — no orphan row lands", + "oracle": "api", + "verify": "scratch throwing beforeInsert; POST → error envelope; before/after filtered count both 0 (mirrors the dogfood hef_ref_guard beforeDelete → REST error-body pin in hook-error-format.dogfood.test.ts)", + "evidence": "the refusal response + the before/after counts" + }, + { + "clause": "onError:'log' on a THROWING async afterUpdate hook does NOT roll the triggering write back: the PATCH persists (re-read shows the change) and the failure is only logged — a fire-and-forget failure can never un-commit a write the engine already resolved", + "oracle": "api", + "verify": "scratch throwing async hook; the PATCH's re-read shows the new value AND the server log carries '[hook] async handler error (fire-and-forget)'", + "evidence": "the persisted re-read + the suppressed-error log line" + }, + { + "clause": "a condition that cannot be evaluated FAILS CLOSED (#4775): the write is ABORTED, not silently skipped — and it is NOT softened by onError:'log' nor fire-and-forgotten, because the condition gate runs OUTSIDE the async/retry/onError wrappers", + "oracle": "api", + "verify": "scratch hook with a broken/uncompilable condition on an object; a write to that object returns a HookConditionError-class refusal and the row does not land (hook-wrappers.ts: conditionFn throws outside runWithErrorPolicy)", + "evidence": "the refusal + a post-attempt count showing no row" + }, + { + "clause": "two hooks on the SAME object+event run lowest-priority-first: scratch hooks at priority 10 and 90 log in that order on one write (hook-binder passes priority to engine.registerHook; the engine orders by it, lower first)", + "oracle": "log", + "verify": "the log shows the priority-10 hook's line before the priority-90 hook's line for a single triggering write", + "evidence": "the two ordered log lines" + } + ], + "negative": [ + "a beforeInsert (or beforeDelete) hook that THROWS under onError:'abort' but leaves the row written (filtered count > 0) is a FAIL — abort must roll the WHOLE write back (the hef_ref_guard dogfood pin is the reference shape)", + "an async onError:'log' hook failure that ROLLS BACK the triggering write is a FAIL — fire-and-forget runs after the write resolved and can only be logged, never un-commit it", + "a SILENT no-fire on a real done:false→true transition is a FAIL — the audit MUST emit; if it truly did not, first rule out that the hooks registered (meta/types/hook) and that the async line was given time to settle before filing (seed-data-thin / async-settle)", + "the audit firing on a NON-transition edit of an already-done task is a FAIL — it would prove the condition collapsed from the two-root transition to a bare `record.done == true` state test (the exact #4784 regression)", + "an unevaluable condition that SILENTLY SKIPS (letting a before* guard through, or dropping an after* audit) instead of aborting the operation is a FAIL — #4775 fails closed, loudly, and onError never sees the condition error" + ], + "traps": [ + "dispatcher-vs-hono-route", + "seed-data-thin", + "stale-dist", + "wrong-persona" + ], + "automated": { + "kind": "dogfood", + "ref": "packages/qa/dogfood/test/showcase-public-form.dogfood.test.ts (pins the beforeInsert stamp: status='new'/source='web' on an anonymous inquiry submit); packages/qa/dogfood/test/hook-error-format.dogfood.test.ts (pins the beforeDelete onError:'abort' throw → REST error body). Declarative-wrapper semantics (two-root condition, async, retry, onError, fail-closed) are unit-pinned in packages/objectql/src/hook-wrappers.ts + hook-binder.ts tests. The transition audit / over-budget warn / priority-ordering LOG oracles and the abort/log/priority SCRATCH variants are NOT yet dogfood-pinned — drive them by hand." + }, + "source": [ + "examples/app-showcase/src/data/hooks/index.ts (the four fixture hooks + allHooks export; header comments spell out the two-root #4784 transition and the != null / not has() #4770 rationale verbatim)", + "packages/spec/src/data/hook.zod.ts (HookSchema + HookEvent enum beforeFind/afterFind/beforeInsert/afterInsert/beforeUpdate/afterUpdate/beforeDelete/afterDelete; defineHook; async 'after* only'; onError default 'abort'; empty-target refusal #4001)", + "packages/objectql/src/hook-wrappers.ts (wrapDeclarativeHook wrapping order condition→async→retry→timeout→onError; pickRecordPayload #4770 record = stored ⊕ payload total over declared fields; pickPreviousPayload #4784 previous binding; HookConditionError #4775 fail-closed, raised OUTSIDE onError; fireAndForget = async && isAfterEvent)", + "packages/objectql/src/hook-binder.ts (bindHooksToEngine: per-event × per-object engine.registerHook with priority; unresolved-body / empty-target skips)", + "packages/runtime/src/sandbox/body-runner.ts:321 (log: engineCtx.logger — the ['log'] capability routing that makes the audit/warn lines an observable log oracle)", + "packages/spec/liveness/hook.json (object/events/body/priority/async/condition/retryPolicy/timeout/onError all 'live'; label/description 'dead' but kept as docs)", + "examples/app-showcase/src/data/objects/{task,inquiry,project}.object.ts (task.done boolean + task.title required; inquiry.status select['new' default:true]/source text no-default; project.budget/spent currency + spent_within_budget rule rejecting spent > budget*1.2)", + "examples/app-showcase/src/data/seed/index.ts (done tasks 'Audit current IA'/'App wireframes'; not-done 'Build homepage'; projects 'Data Platform' 600000/420000 and 'Website Relaunch' 150000/60000)", + "packages/qa/dogfood/test/showcase-public-form.dogfood.test.ts + hook-error-format.dogfood.test.ts (the two existing dogfood pins this item cites)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — the hook coverage.json waiver was STALE (showcase ships 4 observable hooks); authored a lifecycle+condition+async+onError+priority item", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.field-type-constraints", + "title": "Per-type CONSTRAINT enforcement on the write path (not just widget render): length/range/option/reference/computed limits, and where the platform deliberately does NOT enforce", + "since": "v15", + "status": "active", + "revision": 1, + "priority": "P1", + "surface": "mixed", + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_field_zoo (examples/app-showcase/src/data/objects/field-zoo.object.ts): name text maxLength 200; f_number min 0 max 1000; f_currency scale 2 currencyConfig{precision 2} min 0; f_percent min 0 max 100; f_select {low,medium,high}; f_multiselect {red,green,blue}; f_lookup→showcase_account; f_master_detail→showcase_project; f_tree→showcase_category; f_autonumber (default counter, NO autonumberFormat); f_formula = f_number*f_percent/100", + "showcase_invoice (invoice.object.ts): status select {draft,sent,paid,void} required; account lookup with lookupFilters status!=churned; total summary(sum showcase_invoice_line.amount) — the summary carrier the zoo lacks" + ], + "knownGaps": [ + "precision/scale is DECLARED but NOT enforced on the write path — record-validator.ts validateOne's number branch checks only min/max/finite, no runtime reads field.scale/precision for rounding (grep clean across objectql/rest/runtime), and the SQL driver stores number/currency/percent as table.float (REAL affinity), not DECIMAL(p,s) (sql-driver.ts createColumn ~7354-7368). A currency value with more decimals than `scale` is stored verbatim, unrounded. Record it as a gap; do NOT tick 'scale enforced'.", + "autonumberFormat TOKEN expansion ({0000}/{YYYY}/{MM}/{DD}/{YYYYMMDD}/{field}, per-rendered-prefix scope+reset) has NO showcase fixture — no showcase object declares autonumberFormat (f_autonumber uses the default bare 4-width global counter). Token expansion is pinned instead by packages/spec/src/data/autonumber-format.test.ts + packages/drivers/driver-sql/src/sql-driver-autonumber.test.ts; only server-assignment + client-value-ignored is driveable on the zoo.", + "the #4441 reference gate is EXISTENCE-only and deliberately unscoped (engine.ts assertReferencesResolve) — an id that exists but lies outside the field's lookupFilters/dependsOn scope is accepted; scope enforcement is client-only (drilled in records-forms.cascading-multilevel-and-clear).", + "summary is a runtime-MAINTAINED cache, not a write-rejected readonly field — a caller-supplied initial total is a SUPPORTED import path and is kept, then self-heals to the child aggregate on the next child write (rule-validator.ts RUNTIME_OWNED_FIELD_TYPES note, #6014). Only `formula` is strictly read-only-on-write; do NOT assert 'summary rejects direct writes'." + ] + }, + "variants": [ + "text", + "textarea", + "email", + "url", + "phone", + "number", + "currency", + "percent", + "date", + "datetime", + "time", + "select", + "radio", + "multiselect", + "checkboxes", + "tags", + "lookup", + "master_detail", + "tree", + "autonumber", + "formula", + "summary" + ], + "steps": [ + "boot showcase isolated; sign in as the seeded admin; capture an API token for direct /api/v1/data/* POST/PATCH/GET", + "STRING length: POST /api/v1/data/showcase_field_zoo with name = a 201-char string (maxLength 200) and capture the refusal; then POST a 200-char name and capture success — confirm the long value was NOT silently truncated-and-stored", + "NUMBER range: POST f_number = -1, then = 1001, then a non-number ('abc'); capture each refusal; POST f_number = 500 and re-read verbatim", + "SCALE (gap probe): POST f_currency = 1234.567 (declared scale 2); GET the row and record whether the stored value is rounded (expected: stored 1234.567 verbatim — NOT enforced)", + "SELECT server boundary: POST f_select = 'not-a-value' AND (second carrier) POST showcase_invoice status = 'archived'; capture both invalid_option refusals with their options[] echo; confirm neither row count grew", + "MULTISELECT shape: POST f_multiselect = 'red' (a scalar) and re-read as ['red'] (coerced to array); POST f_multiselect = {x:1} (a non-array object) → capture invalid_type; POST f_multiselect = ['red','purple'] → capture the per-element invalid_option naming 'purple'; POST ['red','blue'] → re-read as a set", + "REFERENCE: POST f_lookup = a fabricated account id → capture reference_not_found; POST f_lookup = a real seeded account id → re-read verbatim (note: an EXISTING but out-of-scope id is accepted — see cascading-multilevel-and-clear)", + "AUTONUMBER server-assigned: POST a row with f_autonumber = 'HACK-9999'; GET it and confirm the stored value is the engine's sequence number (client value dropped, #5503); PATCH f_autonumber on an existing row and confirm the change is dropped while the call returns success", + "FORMULA read-only: POST a row writing f_formula = 999 directly; GET and confirm f_formula equals f_number*f_percent/100 (the write was ignored); with f_number=42, f_percent=75 the read is 31.5", + "SUMMARY (nuance): create a showcase_invoice + lines; GET total and confirm it equals the line sum server-side; separately confirm a caller-supplied initial total on import is retained (maintained cache, not rejected) — do not treat summary as write-rejected" + ], + "acceptance": [ + { + "clause": "text/textarea/email/url/phone length is a REJECT, not a truncation: a value longer than the field's maxLength returns 400 VALIDATION_FAILED with fields[] {field, code:'max_length', constraint:{maxLength, actual}} and stores NOTHING — the over-length value is never silently truncated to fit", + "oracle": "api", + "verify": "the 201-char name POST returns max_length naming maxLength 200 & actual 201; the row count does not grow; a 200-char name succeeds. Codes are the ADR-0114 FieldErrorCode catalog (packages/spec/src/api/errors.zod.ts); enforcement in packages/objectql/src/validation/record-validator.ts validateOne string branch", + "evidence": "both refusal/success responses + before/after row counts" + }, + { + "clause": "number/currency/percent range is bounded both sides server-side: below `min` → code 'min_value' {min}; above `max` → 'max_value' {max}; a non-finite value → 'invalid_number'; an in-range value round-trips verbatim", + "oracle": "api", + "verify": "f_number (min 0 max 1000) rejects -1 (min_value) and 1001 (max_value) and 'abc' (invalid_number); 500 re-reads as 500. record-validator.ts validateOne number branch", + "evidence": "the three refusals + the in-range re-read" + }, + { + "clause": "KNOWN GAP recorded, not ticked: `scale`/`precision` are NOT enforced on write — a currency value with more decimals than the declared scale is stored VERBATIM (float column, validator checks only min/max/finite). The run records the observed unrounded value as a gap and must NOT report 'scale enforced'", + "oracle": "api", + "verify": "POST f_currency 1234.567 (scale 2) succeeds and GET returns 1234.567 unrounded — corroborated by record-validator.ts (no scale rounding) and sql-driver.ts createColumn (table.float, not DECIMAL). A run that shows rounding-to-2 would be a NEW enforcement to file, not a pass here", + "evidence": "the write response + the unrounded re-read" + }, + { + "clause": "closed-set types reject a non-declared option value SERVER-side on at least two carriers (not just the widget): f_select and showcase_invoice.status each refuse an out-of-set value with code 'invalid_option' and an options[] listing the allowed set; the row count does not grow", + "oracle": "api", + "verify": "direct POSTs f_select='not-a-value' and status='archived' both return invalid_option carrying options[]; record-validator.ts select/radio branch (allowed = optionValues(def.options))", + "evidence": "both refusal envelopes (with options[]) + before/after counts" + }, + { + "clause": "multiselect storage/read shape is an ARRAY: a lone scalar is coerced to a 1-element array (normalizeMultiValueFields), a non-array object is rejected 'invalid_type', an element outside options is rejected 'invalid_option' echoing the offending element, and a valid array round-trips compared as a set", + "oracle": "api", + "verify": "f_multiselect: 'red'→['red']; {x:1}→invalid_type; ['red','purple']→invalid_option naming 'purple'; ['red','blue']→set-equal on re-read. record-validator.ts multi-value branch + normalizeMultiValueFields", + "evidence": "the four responses + re-reads" + }, + { + "clause": "reference types (lookup/master_detail/tree/user) store the id verbatim and reject a DANGLING id with code 'reference_not_found' {target} for a non-system caller (#4441) — with the boundary that the gate is EXISTENCE-only: an id that exists but is out of lookupFilters/dependsOn scope is accepted (unscoped by design)", + "oracle": "api", + "verify": "a fabricated f_lookup id → reference_not_found; a real seeded account id → verbatim re-read; engine.ts assertReferencesResolve + referenceExists (isSystem/readonly/caller-supplied narrowing). Pinned by packages/objectql/src/engine-lookup-referential-integrity.test.ts", + "evidence": "the dangling refusal + the verbatim re-read + a note that an out-of-scope existing id is accepted" + }, + { + "clause": "autonumber is server-assigned and a client value is IGNORED on both write paths: a POSTed f_autonumber is replaced by the engine sequence value (stripRuntimeOwnedFields, #5503) and a PATCH of it is dropped — the call returns success while the column holds the generated number, never the forged one", + "oracle": "api", + "verify": "POST f_autonumber='HACK-9999' re-reads as a sequence number, not 'HACK-9999'; PATCH is a no-op on the field. rule-validator.ts RUNTIME_OWNED_FIELD_TYPES=['autonumber'] + stripRuntimeOwnedFields/stripReadonlyFields. Pinned by packages/objectql/src/engine-autonumber-runtime-owned.test.ts", + "evidence": "the create re-read + the PATCH no-op re-read" + }, + { + "clause": "formula is read-only/computed: a direct write to f_formula is ignored and the read equals f_number*f_percent/100 (42*75/100 = 31.5); summary is a runtime-MAINTAINED cache (invoice.total = Σ line.amount server-side) and is NOT write-rejected — a caller's initial total is a supported import path that self-heals on the next child write", + "oracle": "api", + "verify": "GET f_formula after a direct write shows 31.5 (write ignored); GET showcase_invoice.total equals the seeded line sum (objectui e2e/live/summary-rollup.spec.ts). The formula/summary distinction: rule-validator.ts note (formula computed-on-read; summary maintained cache, deliberately NOT in RUNTIME_OWNED)", + "evidence": "the formula re-read + the invoice total re-read" + } + ], + "negative": [ + "a 201-char value silently TRUNCATED to 200 and stored with 200/201 is a FAIL — the contract is reject-not-truncate (max_length), and a truncating write corrupts the value with no signal", + "an out-of-set select/status value accepted with 200 is a FAIL — the record-validator, not the picker, is the boundary", + "a client-supplied f_autonumber persisted verbatim is a FAIL — a forged business identifier bypassing the sequence (#5503)", + "a currency value rounded to `scale` and reported as 'scale enforced' is a FALSE PASS — the platform does not round on write; the honest verdict is the recorded gap", + "a dangling lookup id accepted with 200 is a FAIL (#4441)" + ], + "traps": [ + "hydration-race", + "automation-input", + "stale-console-bundle" + ], + "automated": { + "kind": "api", + "ref": "packages/qa/dogfood/test/field-zoo-roundtrip.dogfood.test.ts (+ field-zoo.matrix.ts vectors, field-zoo-value-shape.test.ts) for round-trip & masking; packages/objectql/src/engine-lookup-referential-integrity.test.ts for reference_not_found (#4441); packages/objectql/src/engine-autonumber-runtime-owned.test.ts for the autonumber strip (#5503)" + }, + "source": [ + "packages/objectql/src/validation/record-validator.ts (validateOne: max_length/min_length, min_value/max_value/invalid_number, invalid_option for select+multiselect, normalizeMultiValueFields; NO scale/precision rounding)", + "packages/spec/src/api/errors.zod.ts (FieldErrorCode catalog — required, max_length, min_value/max_value, invalid_option, reference_not_found, invalid_type … ADR-0114)", + "packages/spec/src/data/field.zod.ts (per-type constraints: maxLength/minLength, precision/scale/min/max, options value-vs-label, lookupFilters/dependsOn, autonumberFormat tokens, formula expression, summaryOperations)", + "packages/drivers/driver-sql/src/sql-driver.ts (createColumn: number/currency/percent → table.float, NOT DECIMAL; datetime → DATETIME(3)/timestamptz)", + "packages/objectql/src/engine.ts (assertReferencesResolve #4441 — existence-only, unscoped)", + "packages/objectql/src/validation/rule-validator.ts (RUNTIME_OWNED_FIELD_TYPES=['autonumber']; stripRuntimeOwnedFields/stripReadonlyFields; formula-vs-summary note #6014)", + "packages/spec/src/data/autonumber-format.ts (token grammar) + autonumber-format.test.ts", + "examples/app-showcase/src/data/objects/field-zoo.object.ts, invoice.object.ts (status/account/total carriers)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — drills the field-type-matrix's shallow 'reject invalid' clause into per-type CONSTRAINT enforcement on the write path (length reject-not-truncate, numeric range, option server-boundary on two carriers, multiselect array shape, dangling-reference gate + its unscoped boundary, autonumber server-assignment, formula read-only) and records the deliberate non-enforcement gaps (scale/precision, autonumberFormat tokens, summary maintained-not-rejected)", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.cascading-multilevel-and-clear", + "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": 1, + "priority": "P1", + "surface": "mixed", + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_cascade (examples/app-showcase/src/data/objects/cascading-select.object.ts): country → province, province dependsOn ['country'] with per-option visibleWhen (cn→zj/gd, us→ca/tx); sharingModel public_read_write", + "showcase_invoice.contact dependsOn ['account'] (invoice.object.ts) — the dependent LOOKUP twin; account carries lookupFilters status!=churned; seed spread Northwind/Contoso/Fabrikam (examples/app-showcase/src/data/seed/index.ts)" + ], + "knownGaps": [ + "NO 3-level (grandparent→parent→child) dependsOn fixture exists in the showcase — the deepest chain is 2-level (country→province). The 'changing the grandparent clears/revalidates BOTH descendants' behavior cannot be driven; blocked on a fixture (a 3-level cascade object). The 2-level clear (clause 0) and the server WRITTEN-value gate (clause 1) are the driveable depth.", + "SELECT cascade server enforcement is WRITTEN-value-only: evaluateOptionVisibility re-checks a picked option only when that field is IN the write payload (rule-validator.ts: `!(name in data) continue`). A PATCH that changes ONLY the parent and omits the now-invalid child does NOT revalidate it server-side (clause 2) — server-side cascade integrity on the change-parent path depends entirely on the CLIENT clear. Recorded as a product gap.", + "LOOKUP cascade scope is NOT server-enforced at all: the #4441 reference gate (engine.ts assertReferencesResolve) is deliberately EXISTENCE-only/unscoped, so a directly-POSTed contact that belongs to a DIFFERENT account is accepted as long as the row exists (clause 4). dependsOn/lookupFilters is a picker-side narrowing; the only server reference guard is dangling→reference_not_found." + ] + }, + "steps": [ + "boot showcase isolated; sign in as the seeded admin; keep an API token for direct /api/v1/data/* writes", + "SELECT client clear: open a New Cascading Select (/_console/apps/com.example.showcase/showcase_cascade); pick country=cn, province=zj; screenshot; switch country=us; screenshot; enumerate province options (expect {California,Texas}) and confirm zj CLEARED and not carried into the us submit", + "SELECT server gate (written value): direct POST /api/v1/data/showcase_cascade {country:'cn', province:'ca'} → capture the refusal; then {country:'cn', province:'zj'} → capture success", + "SELECT stale-child EDGE (gap probe): create {country:'cn', province:'zj'}; PATCH only {country:'us'} (omit province); GET the row and record whether province='zj' (now invalid for us) is still stored and the PATCH returned 200 — expected: accepted, stale value kept (server does not revalidate an unwritten child)", + "LOOKUP twin client re-query + clear: on a New Invoice, choose account=Northwind and capture the contact picker's candidate request (scoped to Northwind); pick a Northwind contact; switch account=Contoso; capture the re-issued (Contoso-scoped) candidate request and confirm the Northwind contact CLEARED from the field", + "LOOKUP scope server boundary (gap probe): direct POST /api/v1/data/showcase_invoice with account=<Contoso id> and contact=<a Northwind contact id that exists>; GET and record whether the cross-account contact was accepted (expected: 200, existence-only gate) vs a fabricated contact id (expected: reference_not_found)", + "persistence: create country=cn province=zj (and, for the twin, an invoice with account=Northwind contact=<Northwind contact>) and re-read both verbatim after reload — no orphaned/invalid child" + ], + "acceptance": [ + { + "clause": "SELECT client clear-on-change: changing country re-filters province to the new country's set AND clears a now-invalid prior selection — the stale province is not carried into the submit", + "oracle": "dom", + "verify": "after screenshots confirm render, enumerate province options at cn (zj,gd) then us (ca,tx) and confirm zj cleared; pinned by objectui e2e/live/cascading-options.spec.ts ('province options re-filter live as country changes, and the stale value clears')", + "evidence": "before/after screenshots + option enumerations + the cleared field state" + }, + { + "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", + "evidence": "both responses" + }, + { + "clause": "KNOWN GAP recorded: a parent-only PATCH does NOT revalidate an unwritten stale child — after create {cn,zj} a PATCH {country:'us'} (province omitted) is accepted 200 and the row still holds province='zj' (invalid for us). The server checks only WRITTEN choice fields; the client clear is the only thing that keeps the change-parent path consistent", + "oracle": "api", + "verify": "the create → parent-only PATCH → GET sequence shows province='zj' retained under country='us'; grounded in rule-validator.ts evaluateOptionVisibility `!(name in data) continue`. A run that shows the server auto-clearing/rejecting the stale child would be NEW enforcement to file, not a pass here", + "evidence": "the create, the parent-only PATCH response, and the GET showing the retained stale child" + }, + { + "clause": "LOOKUP twin re-queries and clears: the invoice contact picker issues account-scoped candidate requests, switching account re-issues a request scoped to the new account, AND a previously-chosen contact from the old account is cleared from the field", + "oracle": "network", + "verify": "captured picker requests carry the account scope and the candidate set changes with the account (counts track the seed spread); the field value clears on the account switch (screenshot+dom corroboration). invoice.object.ts contact dependsOn ['account']", + "evidence": "both picker request traces + counts + the cleared-field screenshot" + }, + { + "clause": "KNOWN GAP recorded: LOOKUP cascade scope is NOT server-enforced — a directly-POSTed contact that belongs to a DIFFERENT account but EXISTS is accepted (200), while only a nonexistent contact id is refused with 'reference_not_found'. dependsOn/lookupFilters narrows the picker for UX only; the server reference gate is existence-only by design", + "oracle": "api", + "verify": "POST invoice {account:<Contoso>, contact:<existing Northwind contact>} → 200; POST with a fabricated contact id → reference_not_found. engine.ts assertReferencesResolve is deliberately unscoped (its own 'Why the probe is unscoped' note). A run that shows the cross-account contact rejected would be NEW scope enforcement to file", + "evidence": "the cross-account 200 + the fabricated-id reference_not_found" + }, + { + "clause": "a legal cascade selection persists consistently: create country=cn province=zj (and account=Northwind contact=<Northwind contact> on the twin) re-reads verbatim after reload — no orphaned or invalid child value survives the round-trip", + "oracle": "api", + "verify": "GET both created rows; the cascade pairs are present and mutually consistent", + "evidence": "the two re-reads" + } + ], + "negative": [ + "a stale invalid child value that IS in the write payload accepted with 200 is a FAIL — the WRITTEN-value gate (evaluateOptionVisibility) must reject it (client hiding is UX, not the boundary)", + "an out-of-set / parent-inconsistent province accepted with 200 (child in payload) is a FAIL", + "ticking 'the server enforces the cascade' on the strength of the SELECT written-value gate is a FALSE PASS for the two recorded gaps: the parent-only PATCH (stale child kept) and the LOOKUP scope (cross-account contact accepted) are BOTH client-only today" + ], + "traps": [ + "hydration-race", + "automation-input", + "stale-console-bundle", + "wrong-persona" + ], + "automated": { + "kind": "e2e", + "ref": "objectui: e2e/live/cascading-options.spec.ts (client clear-on-change); packages/objectql/src/validation/rule-validator.option-visibility.test.ts (server WRITTEN-value gate)" + }, + "source": [ + "packages/objectql/src/validation/rule-validator.ts (evaluateOptionVisibility — WRITTEN-value only via `!(name in data) continue`; fail-open on unevaluable; invalid_option/option_unavailable)", + "packages/objectql/src/engine.ts (assertReferencesResolve #4441 — existence-only, 'Why the probe is unscoped')", + "examples/app-showcase/src/data/objects/cascading-select.object.ts (2-level country→province; no 3rd level)", + "examples/app-showcase/src/data/objects/invoice.object.ts (contact dependsOn ['account'], account lookupFilters)", + "examples/app-showcase/src/data/seed/index.ts (contact spread per account)", + "objectui: e2e/live/cascading-options.spec.ts" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — drills the cascade EDGES cascading-options asserts shallowly: stale-child clear-on-change (client), the WRITTEN-value server gate, and the two recorded product gaps (parent-only PATCH does not revalidate an unwritten child; LOOKUP dependsOn scope is existence-only/unscoped server-side). 3-level chain recorded as a fixture gap (no showcase fixture deeper than 2 levels)", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] + }, + { + "id": "records-forms.encrypted-field-behavior", + "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": 1, + "priority": "P1", + "surface": "mixed", + "personas": [ + "seeded admin (admin@objectos.ai / admin123)" + ], + "fixtures": { + "app": "showcase", + "requires": [ + "showcase_field_zoo.f_secret = Field.secret (encrypted-at-rest) and f_password = Field.password({ackPlaintextMasking:true}) (examples/app-showcase/src/data/objects/field-zoo.object.ts)", + "a CryptoProvider-wired boot: `os serve` wires LocalCryptoProvider (AES-256-GCM, OS_SECRET_KEY or a persisted dev key) via engine.setCryptoProvider when the data engine + service-settings resolve (packages/cli/src/commands/serve.ts) — so on a stock dev boot f_secret writes SUCCEED (ciphertext); sys_secret platform object must be registered" + ], + "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 not observed on a stock `os serve` boot (which wires LocalCryptoProvider by default) — it is observed by deliberately unwiring the provider (no OS_SECRET_KEY under the production guard, or a boot without service-settings), or via the unit pin (secret-fields.test.ts + engine.ts encryptSecretFields throw). 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/<run>/data.db) — where the API is the only surface, assert the mask on read + the absence of plaintext in any API response instead." + ] + }, + "steps": [ + "boot showcase isolated on a provider-wired boot (`os serve`, LocalCryptoProvider); sign in as the seeded admin; keep an API token", + "WRITE ciphertext: POST /api/v1/data/showcase_field_zoo {name:'sec-1', f_secret:'topsecret-value'}; GET the row over the API and confirm f_secret reads back as the SECRET_MASK, never 'topsecret-value'; where DB access exists, confirm the business-row column holds a 'secret:<handle>' ref and the plaintext lives only (encrypted) in sys_secret", + "MASK on read for f_password too: POST f_password='p@ssw0rd!'; GET and confirm f_password reads back as the mask (generic password: plaintext-at-rest but masked-on-read)", + "ECHOED-MASK no-op: PATCH the row's f_secret back as the SECRET_MASK sentinel; GET and confirm the stored secret is UNCHANGED (a form round-trip re-submitting the mask does not wipe it)", + "FAIL-CLOSED: on a boot with NO CryptoProvider registered (deliberately unwired), POST f_secret='x' and capture the refusal; confirm NOTHING was persisted (no row, or the field left unset) — cleartext never lands", + "AGGREGATE guard: issue an analytics/group-by request that MIN/MAX/GROUP BYs f_secret (or f_password) and capture the rejection (a credential column is not aggregable — inference-oracle guard)", + "cross-check the negatives: scan every API response captured above and confirm no plaintext 'topsecret-value' / 'p@ssw0rd!' appears anywhere" + ], + "acceptance": [ + { + "clause": "secret WRITE stores ciphertext, never plaintext: after POSTing f_secret, the business row holds an opaque 'secret:<handle>' ref (SECRET_REF_PREFIX) — the plaintext is encrypted into sys_secret via the ICryptoProvider — and no API read echoes the written value", + "oracle": "api", + "verify": "GET returns the mask, not 'topsecret-value'; where DB-inspectable, the column value starts with 'secret:' and sys_secret holds the ciphertext. engine.ts encryptSecretFields (cryptoProvider.encrypt → sys_secret → makeSecretRef); packages/objectql/src/secret-fields.ts", + "evidence": "the write response + the masked GET + (where available) the raw column / sys_secret excerpt" + }, + { + "clause": "reads MASK for every reader: a GET of the record returns f_secret and f_password as SECRET_MASK '••••••••', not the written value — masking on the generic read path is unconditional (plaintext requires an explicit engine decrypt the API read never performs), so even a privileged reader gets the mask", + "oracle": "api", + "verify": "both credential fields read back masked; collectMaskedReadFields (secret always; password unless managedBy:'better-auth'). Pinned by packages/qa/dogfood/test/field-zoo.matrix.ts kind:'masked' (f_secret, f_password) + packages/objectql/src/secret-fields.test.ts", + "evidence": "the masked GET + the pin output" + }, + { + "clause": "an echoed mask is a NO-OP, not a clobber: PATCHing f_secret back as the SECRET_MASK sentinel drops the key and leaves the stored secret unchanged — a form round-trip that re-submits the mask does not wipe the secret", + "oracle": "api", + "verify": "after the mask-PATCH the secret still resolves (mask on read, decrypts to the original where checked); engine.ts encryptSecretFields echoed-mask drop (value === SECRET_MASK ⇒ delete key)", + "evidence": "the mask-PATCH response + the post-PATCH read showing the secret intact" + }, + { + "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. On a stock `os serve` boot the provider IS wired, so this is a deliberately-unwired or unit-pin verdict", + "evidence": "the refusal + a GET/list confirming no persisted cleartext" + }, + { + "clause": "a credential column cannot be AGGREGATED: a MIN/MAX/GROUP BY over f_secret or f_password is rejected unconditionally (even on a better-auth object) — an inference-oracle guard keyed off collectCredentialFields (ADR-0100 / #3171)", + "oracle": "api", + "verify": "the group-by/aggregate request over the credential field returns a rejection, not a leaked value distribution; packages/objectql/src/secret-fields.ts collectCredentialFields", + "evidence": "the aggregate rejection response" + } + ], + "negative": [ + "any API read returning f_secret/f_password plaintext is a FAIL regardless of what the form shows", + "an f_secret write that persists cleartext to the business row — or succeeds at all with NO provider registered — is a FAIL (fail-open is exactly what ADR-0100 forbids)", + "a re-submitted SECRET_MASK that WIPES the stored secret is a FAIL (the echoed mask must be a no-op)", + "an aggregate over a credential column that returns a value distribution is a FAIL (inference oracle)" + ], + "traps": [ + "hydration-race", + "automation-input", + "stale-console-bundle" + ], + "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)" + }, + "source": [ + "packages/objectql/src/secret-fields.ts (SECRET_MASK, SECRET_REF_PREFIX, collectSecretFields/collectMaskedReadFields/collectCredentialFields)", + "packages/objectql/src/engine.ts (encryptSecretFields — encrypt→sys_secret→ref, echoed-mask drop, fail-closed throw; decrypt path)", + "packages/spec/src/data/field.zod.ts (FieldType 'secret'/'password' ADR-0100 notes; ackPlaintextMasking)", + "packages/cli/src/commands/serve.ts (LocalCryptoProvider host wiring via setCryptoProvider)", + "packages/platform-objects/src/system/sys-secret.object.ts (ciphertext store)", + "examples/app-showcase/src/data/objects/field-zoo.object.ts (f_secret, f_password); packages/qa/dogfood/test/field-zoo.matrix.ts (masked vectors)" + ], + "history": [ + { + "revision": 1, + "date": "2026-08-08", + "change": "initial — drills the field-type-matrix's shallow 'credential types mask on read' clause into the full ADR-0100 secret contract: ciphertext-at-rest (sys_secret ref), unconditional read mask for every reader, echoed-mask no-op, fail-CLOSED without an ICryptoProvider, and the credential-aggregate guard. Records that `os serve` wires LocalCryptoProvider by default (fail-closed observed by unwiring) and that f_secret is unseeded", + "ref": "claude/platform-test-checklist-ocwugl" + } + ] } ] } \ No newline at end of file diff --git a/docs/qa/platform-checklist/areas/studio-authoring.json b/docs/qa/platform-checklist/areas/studio-authoring.json index ad2271da89..03b51be3f4 100644 --- a/docs/qa/platform-checklist/areas/studio-authoring.json +++ b/docs/qa/platform-checklist/areas/studio-authoring.json @@ -742,6 +742,288 @@ "history": [ { "revision": 1, "date": "2026-08-08", "change": "new item: permission-matrix editor UX — field-filter + bulk apply scoped to exactly the visible fields (wrong scope writes wrong grants) and the Bulk column's anti-clip min-width, grounded in PermissionMatrixEditor.tsx + its fieldBulk/readonly tests; cross-refs the permission-matrix-edit-loop for the grant→access side", "ref": "claude/platform-test-checklist-ocwugl" } ] + }, + { + "id": "studio-authoring.custom-page-render-and-blocks", + "title": "Declarative custom pages render their real block COMPOSITION (not just 'no error'): each declared block resolves its renderer and binds its data source, data-bound blocks show seeded rows (API cross-check), and a block that cannot bind degrades with a NAMED error, never a blank region", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": ["admin (seeded admin@objectos.ai) driving the published showcase app as an end user"], + "fixtures": { + "app": "showcase", + "requires": [ + "a fresh boot of the showcase with seeded data present — GET /api/v1/data/showcase_project · /showcase_task · /showcase_account must each return rows, and the chart datasets showcase_task_metrics / showcase_project_metrics (examples/app-showcase/src/ui/datasets/chart-gallery.dataset.ts) must resolve (seed-data-thin guard: an empty seed makes every data-bound block look 'broken')", + "a scratch page name (e.g. qa_page_probe) authorable via the draft door PUT /api/v1/meta/page/qa_page_probe?mode=draft — used ONLY for the degradation probe, so no shipped showcase page is mutated" + ], + "knownGaps": [ + "objectui e2e/live/showcase-smoke.spec.ts sweeps only a SUBSET of these pages (My Work, Project Workspace, Active Projects, Component Gallery are in its SURFACES list; Command Center 大屏 / showcase_command_center is NOT) and only asserts render-clean (no pageerror / no placeholder leak / non-empty <main>). This item DEEPENS that shallow pin: it asserts the block composition and the data binding, which the smoke does not — so it is NOT marked `automated`.", + "command-center's KPIs read via the console adapter's aggregate path; where the exact aggregate endpoint differs from a plain GET, cross-check the tile against the same filter applied to the data plane (count of the matching rows), not against a pixel." + ] + }, + "steps": [ + "boot the showcase, sign in admin@objectos.ai/admin123, and confirm the seed is not thin: GET /api/v1/data/showcase_project, /showcase_task, /showcase_account each return rows (record the counts — the data-bound clauses read them back)", + "Command Center (大屏): open /_console/apps/showcase_app/page/showcase_command_center (the objectui smoke drives the package-id form /apps/com.example.showcase/page/showcase_command_center); screenshot the full-bleed board FIRST (confirm render), THEN read the region DOM", + "object-metric KPIs (kind:'full', variant:'bare'): read the 6 hero tiles and cross-check them against the data plane — '活跃项目 Active' == count of showcase_project where status=active, '待办任务 Open' == count of showcase_task where status != done, '风险项目 At-Risk' == count of showcase_project where health=red (aggregate authored in command-center.page.ts)", + "object-chart (bar / donut / area) bound to datasets: confirm each chart drew a sized SVG (no recharts width(-1)/height(-1) collapse) with more than one bucket, and cross-check the '任务状态分布' bar buckets against the showcase_task_metrics dataset aggregation (dimensions ['status'], values ['task_count'])", + "object-grid (待审核列表 work queue) bound to showcase_task: the grid's rows and its columns (title/project/status/priority/due_date) match GET /api/v1/data/showcase_task", + "My Work: open /_console/apps/showcase_app/page/showcase_my_work — the personal object-grid is filtered `owner_id = {current_user_id}`; cross-check its rows against GET /api/v1/data/showcase_task filtered to the signed-in user's id, and confirm the page:card 'Leadership View' (visible: user.email == 'admin@objectos.ai') renders for admin", + "Project Workspace: open /_console/apps/showcase_app/page/showcase_project_workspace — the object-master-detail-form renders the showcase_project parent form AND the inline Tasks child (the block composes parent + children, not a bare error)", + "Active Projects (type:'list', interfaceConfig): open /_console/apps/showcase_app/page/showcase_active_projects — the always-on base filterBy (status != completed) hides completed rows, the default sort (budget desc) orders them, and the grid/kanban visualization switch + health/status userFilters render", + "degradation probe (scratch page, no shipped file touched): PUT /api/v1/meta/page/qa_page_probe?mode=draft a type:'app' page with (a) an object-grid whose objectName is a NONEXISTENT object and (b) a component whose `type` is a bogus string; publish; open /_console/apps/showcase_app/page/qa_page_probe and screenshot — each bad block must surface a NAMED error (SchemaRenderer 'Unknown component type: <t>' red panel / the SchemaErrorBoundary 'Component … failed to render' panel / the block's own empty-or-error state), never a silently blank region", + "capture the server log across the whole sweep (the render-clean and no-restart baseline)" + ], + "acceptance": [ + { + "clause": "every block TYPE declared on the shipped declarative pages resolves to a real renderer — no region renders the OBJUI-001 'Unknown component type' panel or the SchemaErrorBoundary failure panel on command-center / my-work / project-workspace / active-projects; the composition the page authored is the composition on screen", + "oracle": "dom", + "verify": "after a screenshot confirms each page rendered, the region DOM contains each declared block's rendered output (a chart SVG, a grid table, a metric tile, the master-detail form) and NOT a role=alert error/'Unknown component type' panel", + "evidence": "per-page screenshot + region DOM read" + }, + { + "clause": "a data-bound block shows SEEDED rows, cross-checked against the server not pixels — command-center's object-grid rows equal GET /api/v1/data/showcase_task, and the '活跃项目 Active' object-metric equals the active-project count from the data plane", + "oracle": "api", + "verify": "the grid's visible rows and the KPI numbers each match the same query/filter run against /api/v1/data/* — a tile or grid that renders but disagrees with the data plane is caught here", + "evidence": "the data reads paired with the tile/grid screenshot" + }, + { + "clause": "object-chart buckets are data-real and multi-bucket: each chart drew a sized SVG (no width(-1)/height(-1) collapse) and its buckets match the dataset aggregation — not a single-datapoint smear that 'renders' but proves nothing", + "oracle": "api", + "verify": "the '任务状态分布' bar buckets equal the showcase_task_metrics aggregate (dimensions status, values task_count); the SVG bounding box has width>0 and height>0", + "evidence": "chart screenshot (sized SVG) + the dataset aggregate read" + }, + { + "clause": "my-work's {current_user_id} filter is SERVER-honoured: the personal grid's rows equal GET /api/v1/data/showcase_task filtered to the signed-in owner, and a task owned by another user is ABSENT — the token resolved to the real actor, not to every row", + "oracle": "api", + "verify": "the grid rows are a subset of showcase_task and equal the owner_id={signed-in id} filtered set; a foreign-owned task id is not among them", + "evidence": "the filtered data read + the grid screenshot" + }, + { + "clause": "the interface (list) page honours its interfaceConfig: active-projects hides completed rows (base filterBy status != completed), orders by budget desc, and exposes the grid/kanban switch plus the health/status user-filters — the page IS the view definition (ADR-0047), not a bare object dump", + "oracle": "screenshot", + "verify": "the rendered list shows no completed rows, biggest-budget first, with the visualization switch and userFilters controls present; cross-check the no-completed-rows claim against the data plane", + "evidence": "the list screenshot + a data read confirming completed rows exist but are filtered out" + }, + { + "clause": "object-master-detail-form composes parent + child: project-workspace renders the showcase_project create form AND the inline Tasks child affordance (grid/add-button), not just the parent or an error", + "oracle": "dom", + "verify": "after a screenshot confirms render, the DOM carries the parent field inputs and the child Tasks section (the master_detail relationship the block auto-derives from showcase_task.project)", + "evidence": "screenshot + the master-detail DOM read" + }, + { + "clause": "a page block that cannot bind degrades with a NAMED error, never a blank — the scratch page's bogus-`type` block renders 'Unknown component type', and the missing-object object-grid renders an attributable error/empty-state (the SchemaErrorBoundary or the block's own message), so a broken binding is SEEN, not swallowed", + "oracle": "screenshot", + "verify": "the qa_page_probe render shows the named error for each bad block; a silently blank region for either would be the failure this clause exists for", + "evidence": "the scratch-page screenshot" + } + ], + "negative": [ + "a data-bound region rendering EMPTY over seeded data with no error and no empty-state (silently swallowed) is a FAIL — the OBJUI-001 'Unknown component type' panel is the CORRECT behaviour for a bad block; a blank is the bug", + "a declared block silently ABSENT (dropped) while the page still 'renders clean' is a FAIL — render-clean (the smoke's bar) is necessary but not sufficient; the block must actually be composed", + "an object-chart 'rendering' as a zero-height or single-bucket smear over a multi-bucket seed is a FAIL (single-datapoint trap)" + ], + "variants": [ + "layout container: flex (command-center) / grid (my-work, component-gallery)", + "structure: page:header / page:card (with a per-user `visible` gate)", + "content: element:text / element:divider", + "data-bound: object-metric (aggregate KPI, variant bare) / object-chart (dataset-bound bar/donut/area) / object-grid (object-bound list, {current_user_id} filter) / object-form (create form) / object-master-detail-form (parent + children)" + ], + "traps": ["hydration-race", "stale-console-bundle", "seed-data-thin", "single-datapoint", "automation-input"], + "source": [ + "packages/spec/src/ui/page.zod.ts (PageComponentType enum — namespaced page:/record:/element: block types; PageComponentSchema.dataSource per-element binding; PageComponentSchema.type = union(enum, string) so objectui-registered custom blocks like object-metric/object-chart/object-grid/object-form/object-master-detail-form/flex/grid are valid)", + "examples/app-showcase/src/ui/pages/command-center.page.ts (object-metric bare KPIs + object-chart bar/donut/area on datasets + object-grid work queue), my-work.page.ts ({current_user_id} object-grid + page:card visible-gate), project-workspace.page.ts (object-master-detail-form), active-projects.page.ts (interfaceConfig list: filterBy/sort/appearance/userFilters/addRecord)", + "objectui: packages/components/src/renderers/layout/page.tsx (PageRenderer — region/template dispatch, full-bleed for width:'full' main)", + "objectui: packages/react/src/SchemaRenderer.tsx (the OBJUI-001 'Unknown component type' red panel + the SchemaErrorBoundary 'Component failed to render' panel — the NAMED-degradation contract; page.<var>/data/user expression scope)", + "objectui: e2e/live/showcase-smoke.spec.ts (the shallow render-clean sweep for my-work/project-workspace/active-projects — this item deepens it with block-composition + data-binding assertions)", + "examples/app-showcase/src/ui/datasets/chart-gallery.dataset.ts (showcase_task_metrics / showcase_project_metrics — the chart bindings)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "new item: declarative custom pages render their real block composition and bind data (API cross-check, not pixels), with a scratch-page probe proving a bad binding degrades to a NAMED error not a blank — grounded in the four showcase declarative page sources, the PageComponentType enum, objectui PageRenderer + SchemaRenderer, and deepening the shallow showcase-smoke render-clean pin", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "studio-authoring.page-variables-and-actions", + "title": "Page variables + page-level actions are runtime-live: variables initialize empty, an interactive writer updates one and dependent visibleWhen predicates re-evaluate WITHOUT reload, and a page action reads the live variable snapshot to POST resolved values that create a real record", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": ["admin (seeded admin@objectos.ai) driving the published showcase app as an end user"], + "fixtures": { + "app": "showcase", + "requires": [ + "seeded showcase_project rows (the element:record_picker on showcase_page_variables lists them)", + "the public web-to-lead endpoint POST /api/v1/forms/contact-us/submit and its showcase_inquiry sink (ADR-0056) reachable on the stock showcase boot — the contact-form submit action posts here" + ], + "knownGaps": [ + "neither showcase_page_variables nor showcase_contact_form is in objectui e2e/live/showcase-smoke.spec.ts's SURFACES sweep — there is no automated render-clean pin for either, so this item is browser-driven end to end (not marked `automated`)" + ] + }, + "steps": [ + "open /_console/apps/showcase_app/page/showcase_page_variables and screenshot the INITIAL state: the empty-hint (element:text, visibleWhen \"page.selectedProjectId == ''\") is VISIBLE and the detail panel (divider/heading/body, visibleWhen \"page.selectedProjectId != ''\") is ABSENT — the variable initialized to its empty value", + "select a project in the element:record_picker (id: project_picker — the `source` of the selectedProjectId variable) with a ref-targeted selection (not a coordinate click); screenshot again: the empty-hint disappears and the detail panel appears LIVE, with NO page reload", + "read the DOM to confirm the toggle is real, not a repaint: the detail-panel nodes are present and the empty-hint absent once the picker wrote page.selectedProjectId", + "open /_console/apps/showcase_app/page/showcase_contact_form; type into the element:text_input id=field_email (native setter + input event); the ready_hint (element:text, visibleWhen \"page.inquiryEmail != ''\") must appear the instant a value is present", + "fill all four inputs (field_name/field_email/field_company/field_message — each the `source` of its string page variable), click the submit element:button (id: submit_inquiry), and capture the network request its `api` action issues to POST /api/v1/forms/contact-us/submit", + "assert the request BODY carries the RESOLVED variable values (name/email/company/message from the typed text), not the literal {{page.inquiryName}} … tokens — the PageVariableActionBridge resolved them from the live snapshot", + "cross-check the server effect: GET /api/v1/data/showcase_inquiry — a new row carrying the entered name/email/company/message", + "negative probe: reload showcase_page_variables without picking — the detail panel stays hidden (the variable re-initialized empty, it did not retain a stale selection)" + ], + "acceptance": [ + { + "clause": "page variables initialize to their empty value and GATE dependent blocks on first render: page.selectedProjectId == '' so the empty-hint shows and the detail panel is withheld — the master/detail surface starts in its empty state, driven by the declared variable", + "oracle": "screenshot", + "verify": "the first render of showcase_page_variables shows the empty-hint and no detail panel", + "evidence": "the initial-state screenshot" + }, + { + "clause": "an interactive writer updates its variable and dependent visibleWhen predicates re-evaluate LIVE: selecting in project_picker (the `source` of selectedProjectId) reveals the detail panel and hides the hint with NO reload — the write re-runs the predicates immediately (ADR-0049 runtime-live variables)", + "oracle": "screenshot", + "verify": "before/after screenshots across a single selection: empty-hint→gone, detail panel→shown, same page load", + "evidence": "before/after screenshots" + }, + { + "clause": "the DOM reflects the toggle, not just pixels: after a screenshot confirms render, the detail-panel nodes are present and the empty-hint absent once a value is written to page.selectedProjectId", + "oracle": "dom", + "verify": "the gated nodes' presence/absence in the DOM matches the variable state (page.<var> in the SchemaRenderer expression scope)", + "evidence": "the before/after DOM reads" + }, + { + "clause": "contact-form's per-field variables bind live: typing into field_email flips the ready_hint visible (page.inquiryEmail != '') — one text input writing one page variable drives another component's visibility", + "oracle": "screenshot", + "verify": "the ready_hint appears the instant an email value is present in the input", + "evidence": "the ready-hint screenshot" + }, + { + "clause": "the page-level action dispatches with RESOLVED variables: the submit button's `api` action POSTs /api/v1/forms/contact-us/submit with params resolved from the live page-variable snapshot (PageVariableActionBridge), NOT literal {{page.<var>}} strings", + "oracle": "network", + "verify": "the captured POST body's name/email/company/message equal the typed values; no field carries an unresolved '{{page.…}}' token", + "evidence": "the submit request body" + }, + { + "clause": "the action's server effect is real: a new showcase_inquiry record carrying the entered fields exists after submit (ADR-0056 web-to-lead) — the page action moved data, it did not just toast", + "oracle": "api", + "verify": "GET /api/v1/data/showcase_inquiry returns a row with the submitted name/email/company/message", + "evidence": "the data read of the new inquiry" + } + ], + "negative": [ + "a detail/ready block visible BEFORE any interaction (the variable was not initialized empty) is a FAIL — the empty state is the declared initial condition", + "a variable write that needs a page RELOAD to take effect is a FAIL — the whole point of runtime-live variables (ADR-0049) is immediate re-evaluation of dependent predicates", + "a submit that POSTs the literal {{page.inquiryName}} token (the bridge did not resolve the snapshot) is a FAIL", + "a 2xx submit that creates NO showcase_inquiry (the action reported success but moved nothing) is a FAIL — server truth is the oracle, not the toast" + ], + "variants": [ + "writer: element:record_picker → record_id variable (page-variables)", + "writer: element:text_input → string variable (contact-form)", + "reader: component visibleWhen `page.<var>` (empty-state / detail gating)", + "reader: page action params `{{page.<var>}}` (submit POST body)" + ], + "traps": ["hydration-race", "stale-console-bundle", "automation-input"], + "source": [ + "packages/spec/src/ui/page.zod.ts (PageVariableSchema — `source` is the WRITER component id, read by predicates as page.<name>; PageSchema.variables runtime-live per ADR-0049; PageComponentSchema.visibleWhen binds record/current_user/page.<var>, ADR-0089)", + "examples/app-showcase/src/ui/pages/page-variables.page.ts (selectedProjectId ← project_picker; empty-hint vs detail gated on page.selectedProjectId), contact-form.page.ts (four string vars ← text_inputs; submit `api` action posting {{page.<var>}} to /api/v1/forms/contact-us/submit → showcase_inquiry)", + "objectui: packages/components/src/renderers/layout/page.tsx (PageVariablesProvider mounts the declared variables; PageVariableActionBridge publishes the live snapshot into the action runtime so a submit resolves {{page.<var>}})", + "objectui: packages/react/src/SchemaRenderer.tsx (page.<var> threaded into the ExpressionEvaluator scope; visibleWhen/visibility re-evaluated on variable change)", + "ADR-0056 (public web-to-lead form → showcase_inquiry), ADR-0089 (visibleWhen canonical predicate)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "new item: the end-to-end page-variable loop (initialize empty → interactive writer updates → dependent visibleWhen re-evaluates live, no reload) and the page-level `api` action resolving {{page.<var>}} from the live snapshot to create a real showcase_inquiry — grounded in PageVariableSchema, the page-variables + contact-form sources, and objectui's PageVariablesProvider/PageVariableActionBridge", "ref": "claude/platform-test-checklist-ocwugl" } + ] + }, + { + "id": "studio-authoring.custom-page-source-tiers", + "title": "Source-authored page tiers render by the RIGHT path: kind:'html' source is PARSED-never-executed (static, in-browser sdui-parser compile), kind:'react' source is EXECUTED behind its capability gate (live data + interactivity) — command-center-jsx / capability-map (html) vs crm-workbench (react)", + "since": "v16", + "status": "active", + "revision": 1, + "priority": "P2", + "surface": "mixed", + "personas": ["admin (seeded admin@objectos.ai) driving the published showcase app as an end user"], + "fixtures": { + "app": "showcase", + "requires": [ + "seeded showcase_project rows (the react page's live KPI + <ListView> read them)", + "the react-pages capability ON — CAP_REACT_PAGES defaults ON; a boot with OS_PAGE_REACT=off flips it OFF (the both-sides gate check)" + ], + "knownGaps": [ + "NO manual build step is needed to VIEW either tier in the running showcase (contra the task's guess): a kind:'html' page compiles IN-BROWSER via @object-ui/sdui-parser compile(source, manifest) at render, against a whitelist manifest built from the LIVE ComponentRegistry (packages/components/src/renderers/layout/page.tsx); a kind:'react' page is transpiled IN-BROWSER by a lazily-loaded Sucrase @object-ui/react-runtime chunk. The only build-shaped risk is the vendored /_console bundle being stale (stale-console-bundle) — a stale sdui-parser/react-runtime mis-compiles a page that is fine on a fresh build.", + "none of showcase_command_center_jsx / showcase_capability_map / showcase_crm_workbench is in objectui e2e/live/showcase-smoke.spec.ts's SURFACES sweep — there is no automated render-clean pin for the html/react tiers, so this item is browser-driven (not marked `automated`)." + ] + }, + "steps": [ + "capability-map (kind:'html', isDefault landing): open /_console/apps/showcase_app/page/showcase_capability_map; screenshot the six protocol-domain cards; confirm the compiled SchemaNode tree rendered (flex/div/a nodes with theme-token style objects), NOT the 'HTML page failed to compile' panel", + "command-center-jsx (kind:'html'): open /_console/apps/showcase_app/page/showcase_command_center_jsx; screenshot the KPI board (Open Tasks 128 / In Progress 47 / Completed 1,902 / Cycle Time 2.4d) and the weekly-throughput bars", + "prove the html tier is PARSED-NEVER-EXECUTED and STATIC: the KPI numbers are hand-authored sample copy — 128 does NOT equal GET /api/v1/data/showcase_task's count, and NO data query fires for those tiles (the page's own on-screen copy says the numbers are static sample copy)", + "crm-workbench (kind:'react'): open /_console/apps/showcase_app/page/showcase_crm_workbench; screenshot the workbench — the KPI strip (Total / Active) + the real <ListView> and <ObjectForm> master/detail over showcase_project", + "prove the react tier is EXECUTED against LIVE data: the 'Total projects' KPI equals GET /api/v1/data/showcase_project's count (the page computes it via useAdapter().find('showcase_project'), reading the QueryResult .data envelope), and a showcase_project find query fires on the network — the number is computed at render, not authored", + "prove react interactivity (executed handlers, not a parsed tree): click a ListView row → the ObjectForm binds THAT record (React useState); click '+ New project' → the form switches to create mode", + "both-sides gate: on a boot with OS_PAGE_REACT=off, re-open crm-workbench — it must render the NAMED 'React pages are disabled on this deployment' notice (CapabilityDisabledNotice), never a blank and never an executed page; the html pages still render (html is not gated). If the env toggle cannot be flipped on the stock fixture, record this clause blocked(environment)", + "degradation: confirm an html compile error would surface the 'HTML page failed to compile (N)' panel with the diagnostic messages, and a react runtime error the 'React page error' fallback — neither a blank page (drive via the scratch draft door if a shipped page cannot be made to fault)" + ], + "acceptance": [ + { + "clause": "the renderer dispatches on `kind`: an html page renders the sdui-parser-COMPILED tree (parsed, never executed) via SchemaRenderer, and a react page renders through ReactKindPage — evidenced by the distinct surfaces each produces (html = static composed board; react = live data + interactivity)", + "oracle": "screenshot", + "verify": "capability-map/command-center-jsx render composed static boards; crm-workbench renders a data-driven interactive workbench — the two paths produce their characteristic surfaces", + "evidence": "the three page screenshots" + }, + { + "clause": "the html tier is STATIC and UNEXECUTED: command-center-jsx's KPI numbers are hand-authored sample copy — they do NOT equal the data-plane counts, and NO data query fires for them (parse-never-execute, ADR-0080)", + "oracle": "api", + "verify": "the on-screen KPI (e.g. Open Tasks 128) differs from GET /api/v1/data/showcase_task's count, and the network shows no find/aggregate request backing those tiles", + "evidence": "the KPI screenshot + the data read (mismatch is the point) + the network trace showing no data query" + }, + { + "clause": "the react tier is EXECUTED against live data: crm-workbench's 'Total projects' KPI equals GET /api/v1/data/showcase_project's count and a showcase_project find query fires — the value is computed at render by author JS (useAdapter().find), not authored (ADR-0081)", + "oracle": "api", + "verify": "the KPI number equals the live showcase_project count, and the network carries the find query the page's useEffect issues", + "evidence": "the KPI screenshot + the data read (match) + the find-query network trace" + }, + { + "clause": "react HANDLERS run (execution, not a parsed tree): selecting a ListView row binds the ObjectForm to that record and '+ New project' switches the form to create mode — React state transitions the html tier could never express", + "oracle": "dom", + "verify": "after a screenshot confirms render, a ref-targeted row click updates the form to the selected record; the New button switches modes", + "evidence": "before/after screenshots + the form DOM read" + }, + { + "clause": "the react capability gate is real and BOTH-sided: with CAP_REACT_PAGES on, crm-workbench executes; with OS_PAGE_REACT=off it renders the NAMED 'React pages are disabled on this deployment' notice — never a blank, never a bypassed execution (the html pages are unaffected)", + "oracle": "screenshot", + "verify": "the on-state executes; the off-state shows CapabilityDisabledNotice with the OS_PAGE_REACT=off text; record blocked(environment) if the toggle cannot be exercised on the stock fixture", + "evidence": "the on-state + off-state screenshots (or the blocked-environment note)" + }, + { + "clause": "compile/runtime failures degrade NAMED, never blank: an html compile error is the 'HTML page failed to compile (N)' panel listing the diagnostics; a react error is the 'React page error' fallback carrying the message", + "oracle": "screenshot", + "verify": "a faulted html source shows the compile-error panel; a faulted react source shows the error fallback — a blank page for either is the failure this clause exists for", + "evidence": "the error-panel screenshot(s)" + } + ], + "negative": [ + "an html page whose source is EXECUTED (author JS runs) rather than parsed is a security FAIL — ADR-0080's html tier is parse-never-execute; execution is the react tier's gated privilege only", + "a react page that executes with OS_PAGE_REACT=off (the gate bypassed) is a FAIL — the capability is the whole safety boundary for the trusted-execution tier", + "a react KPI stuck at 0 while the <ListView> beside it shows rows is the crm-workbench QueryResult-envelope bug regressed (reading .records instead of .data) — FAIL, cross-checked against the live count", + "a compile or runtime error swallowed to a blank page (no named panel) is a FAIL — the author must SEE why the page did not render" + ], + "variants": [ + "html tier: command-center-jsx (composition of registered components with structured props) + capability-map (domain cards + out-links, isDefault landing)", + "react tier: crm-workbench (useAdapter + <ListView>/<ObjectForm> + React useState master/detail)" + ], + "traps": ["stale-console-bundle", "hydration-race", "single-datapoint", "automation-input"], + "source": [ + "packages/spec/src/ui/page.zod.ts (PageSchema.kind = full|slotted|html|react|jsx; html = constrained JSX/HTML+Tailwind compiled by @objectstack/sdui-parser 'parse, never execute' ADR-0080; react = real React executed at render, gated by a host capability defaulting ON, disabled via OS_PAGE_REACT=off, ADR-0081; the superRefine that fails an html/react page with no `source`)", + "objectui: packages/components/src/renderers/layout/page.tsx (PageRenderer kind dispatch — compile(src, getJsxManifest()) + SchemaRenderer for html with the 'HTML page failed to compile' panel; ReactKindPage for react; the manifest whitelist is built from the LIVE registry's known types)", + "objectui: packages/components/src/renderers/layout/react-page.tsx (ReactKindPage — CAP_REACT_PAGES gate + CapabilityDisabledNotice for OS_PAGE_REACT=off; lazy Sucrase @object-ui/react-runtime transpile; useAdapter/ListView/ObjectForm scope; the 'React page error' fallback)", + "objectui: packages/sdui-parser/src/index.ts (compile(source, manifest) — the pure in-browser parse used at render, no build step)", + "examples/app-showcase/src/ui/pages/command-center-jsx.page.ts (kind:'html', static sample KPIs — 'parsed, never executed'), capability-map.page.ts (kind:'html' landing), crm-workbench.page.ts (kind:'react' — useAdapter().find reads the .data envelope, note the #… KPIs-stuck-at-0 fix)", + "objectui: content/docs/guide/react-pages.md (react tier: transpiled + evaluated in-app, react-pages capability, OS_PAGE_REACT=off disable)" + ], + "history": [ + { "revision": 1, "date": "2026-08-08", "change": "new item: the html (parse-never-execute, in-browser sdui-parser compile, static) vs react (executed at render, capability-gated, live data + interactivity) source-page tiers — the objectui renderer-path split grounded in PageRenderer's kind dispatch + react-page.tsx's gate, distinguishing command-center-jsx/capability-map (html) from crm-workbench (react); records the honest build-step finding (none needed to view — in-browser compile/transpile) as a knownGap", "ref": "claude/platform-test-checklist-ocwugl" } + ] } ] } diff --git a/docs/qa/platform-checklist/coverage.json b/docs/qa/platform-checklist/coverage.json index efa72f6d98..7e1c2c33e7 100644 --- a/docs/qa/platform-checklist/coverage.json +++ b/docs/qa/platform-checklist/coverage.json @@ -75,7 +75,9 @@ ] }, "hook": { - "waived": "record hooks are exercised INDIRECTLY by every CRUD/roll-up/attachment item (their side effects are those items' oracles), but no item asserts hook execution itself; needs a fixture hook with an observable, attributable side effect. Candidate follow-up item: hook fires on insert/update/delete with ctx.user populated (PD#9 organizationId contract)." + "items": [ + "records-forms.object-hook-lifecycle" + ] }, "job": { "items": [ diff --git a/scripts/check-platform-checklist.mjs b/scripts/check-platform-checklist.mjs index 3e9a310823..b5bacb0412 100644 --- a/scripts/check-platform-checklist.mjs +++ b/scripts/check-platform-checklist.mjs @@ -143,6 +143,68 @@ for (const file of files) { if (item.automated !== undefined && item.automated !== null) { if (typeof item.automated.ref !== 'string' || !item.automated.ref) where('"automated.ref" must point at the pinning test'); } + + if (item.enumSource !== undefined) { + const es = item.enumSource; + if (typeof es?.file !== 'string' || typeof es?.export !== 'string' || !Number.isInteger(es?.expect)) { + where('"enumSource" needs { file, export, expect } — the spec enum this item\'s variants matrix was authored against'); + } + } + } +} + +// ── Variants-freshness ratchet ────────────────────────────────────────────── +// A matrix item's `variants` list is hand-authored against a spec value enum +// (49 field types, 20 chart types, …). When the spec grows or shrinks that +// enum, nothing used to force the matrix to follow — the drift was only caught +// indirectly by the showcase coverage.test.ts demonstrability gate. `enumSource` +// pins the enum here: {file, export, expect}. This check extracts the CURRENT +// member count from the spec source (comment-stripped, deduped — enum blocks +// carry prose comments quoting member names) and fails when it no longer equals +// `expect`. Fixing the failure = revising the item's variants for the new +// member(s), bumping the item revision, and updating `expect` — exactly the +// "platform grew a capability, the checklist must follow" moment this gate +// exists to force. Extractor rot is loud, not fail-open: a missing file or +// export is an error, never a silent skip. +function extractEnumMembers(absFile, exportName) { + const src = readFileSync(absFile, 'utf8'); + const decl = src.match(new RegExp(`(?:export\\s+)?const\\s+${exportName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b[^=]*=`)); + if (!decl) return null; + const start = src.indexOf('[', decl.index + decl[0].length); + if (start === -1) return null; + let depth = 0; + for (let j = start; j < src.length; j++) { + if (src[j] === '[') depth++; + else if (src[j] === ']') { + depth--; + if (depth === 0) { + const seg = src.slice(start, j + 1) + .replace(/\/\/[^\n]*/g, '') + .replace(/\/\*[\s\S]*?\*\//g, ''); + const seen = new Set(); + for (const m of seg.matchAll(/'([a-zA-Z0-9_\-]+)'/g)) seen.add(m[1]); + return [...seen]; + } + } + } + return null; +} + +for (const { file, item } of allItems) { + const es = item.enumSource; + if (!es || typeof es.file !== 'string' || typeof es.export !== 'string') continue; + const abs = join(ROOT, es.file); + if (!existsSync(abs)) { + err(file, item.id, `enumSource.file not found: ${es.file} — the pinned spec source moved; re-point the pin`); + continue; + } + const members = extractEnumMembers(abs, es.export); + if (members === null) { + err(file, item.id, `enumSource export "${es.export}" not found in ${es.file} — renamed or reshaped; re-point the pin (extractor must stay loud, never fail-open)`); + continue; + } + if (members.length !== es.expect) { + err(file, item.id, `VARIANTS STALE — ${es.export} in ${es.file} now has ${members.length} members but this item's variants were authored against ${es.expect}. The platform grew/shrank this surface: revise the variants matrix, bump the item revision, and set enumSource.expect to ${members.length}.`); } } From 89668658aa7928554235d6f95836be7d6362ce15 Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Sat, 8 Aug 2026 04:17:10 +0000 Subject: [PATCH 08/14] =?UTF-8?q?feat(qa):=20coverage-sweep=20=E5=86=85?= =?UTF-8?q?=E9=83=A8=20skill=20=E2=80=94=20=E4=B8=80=E5=8F=A5=E8=AF=9D?= =?UTF-8?q?=E6=89=8B=E5=8A=A8=E8=A7=A6=E5=8F=91=E6=B8=85=E5=8D=95=E7=BC=BA?= =?UTF-8?q?=E5=8F=A3=E9=87=8D=E6=89=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Manual trigger for the five-angle checklist gap hunt: the maintainer says "跑一轮 coverage sweep" (or asks "X 有测试吗" needing verification) and the AI session loads the orchestration contract — worktree-first, five read-only hunters in parallel, per-area writer partitioning, central coverage.json reconciliation with waiver re-audit, validator-green landing, FOLLOW-UPS for defects. Canonical method stays in docs/qa/platform-checklist/SWEEP.md; the skill is the trigger + contract, not a second copy. metadata.internal: true per template-consistency (sibling to dogfood-verification/pm-dispatch). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YD9f6FYyMraUWYeJf53V43 --- .claude/skills/coverage-sweep/SKILL.md | 61 ++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .claude/skills/coverage-sweep/SKILL.md diff --git a/.claude/skills/coverage-sweep/SKILL.md b/.claude/skills/coverage-sweep/SKILL.md new file mode 100644 index 0000000000..9fef2e080b --- /dev/null +++ b/.claude/skills/coverage-sweep/SKILL.md @@ -0,0 +1,61 @@ +--- +name: coverage-sweep +description: > + Re-audit the platform test checklist (docs/qa/platform-checklist/) for coverage + gaps and author the missing items — the five-angle capability sweep. Use whenever + the maintainer says "跑一轮 coverage sweep", "run a coverage sweep", "排查测试清单 + 遗漏", "审计测试覆盖", or asks whether some platform surface "有测试吗" and the + answer needs verifying rather than recalling. Also the right tool after a large + platform surface lands or before a major release. NOT a customer-published skill — + this is internal agent tooling (lives in .claude/, never in the published + `skills/` dir). +metadata: + # Hides this skill from interactive `npx skills add objectstack-ai/objectstack` + # discovery — every SKILL.md outside `skills/` must carry this marker + # (template-consistency.test.ts enforces it). + internal: true +--- + +# Coverage sweep — keep the platform test checklist honest + +The canonical method lives in **`docs/qa/platform-checklist/SWEEP.md`** — read it +first and follow it; this skill is the trigger and the orchestration contract, not a +second copy of the procedure. + +## What you are producing + +A delta on `docs/qa/platform-checklist/`: new/extended items in `areas/*.json`, a +reconciled `coverage.json`, defects/docs-drift appended to `FOLLOW-UPS.md` — all +validating green under `node scripts/check-platform-checklist.mjs`, landed on a task +branch per AGENTS.md (worktree-first, PD#11). + +## Orchestration contract + +1. **Worktree first** (PD#11): `git worktree add ../objectstack-<task> -b <branch> main`. + All edits there. Read the checklist's current state before dispatching anything. +2. **Five READ-ONLY gap hunters in parallel** — one per SWEEP.md angle (console UI / + spec enums / routes & runtime / built-in apps / docs claims). Each gets: the current + item-id list, the already-known waivers and blocked items (don't re-report), and the + output contract `surface | evidence path | coverage verdict | proposed id | sketch | + fixture?`. Hunters write NO files. +3. **Dedupe into a scratch register** (delete it before landing). Cross-angle + duplicates are high-priority signal, not noise. +4. **Per-area writer agents** — one agent per `areas/*.json` file so writers never + collide; nobody but the orchestrator touches `coverage.json` or `scripts/`. + Every item follows README.md's deep-test contract; missing fixtures become + `blocked`/`knownGaps`, never faked coverage. Writers ground every endpoint, enum, + and error code in source before asserting — treat this skill's own briefs as + hypotheses, source as truth. +5. **Reconcile centrally**: un-waive any kind a hunter proved has a stock fixture + (four of six waivers were stale in the 2026-08 sweep — re-audit every waiver every + time), map new items in `coverage.json`, pin `enumSource` on any new variants + matrix (see README "Variants stay fresh automatically"). +6. **Validate + land**: validator green, then commit on the task branch. Product + defects and docs-drift go to `FOLLOW-UPS.md`; security-sensitive findings are + NEVER filed publicly without the maintainer's decision. + +## Scale guidance + +A full sweep is ~5 hunter + ~8 writer agents. For a scoped question ("X 有测试吗?"), +run ONE hunter on the relevant angle, verify against the checklist, and author only +what's missing — same contract, smaller fleet. From 99a053d461af64aed50adb5679c36525d4f6a283 Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Sat, 8 Aug 2026 04:30:41 +0000 Subject: [PATCH 09/14] =?UTF-8?q?docs(qa):=20FOLLOW-UPS=20=E5=A4=B4?= =?UTF-8?q?=E9=83=A8=E6=8E=AA=E8=BE=9E=E9=9A=8F=E6=8F=90=E4=BA=A4=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YD9f6FYyMraUWYeJf53V43 --- docs/qa/platform-checklist/FOLLOW-UPS.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/qa/platform-checklist/FOLLOW-UPS.md b/docs/qa/platform-checklist/FOLLOW-UPS.md index 79d962004e..9bab64cdd2 100644 --- a/docs/qa/platform-checklist/FOLLOW-UPS.md +++ b/docs/qa/platform-checklist/FOLLOW-UPS.md @@ -1,6 +1,6 @@ # Follow-ups — open items from the capability-coverage sweep (2026-08-08) -Working document, NOT committed pending user confirmation. The gap items found by the +Decision register from the capability-coverage sweep. The gap items found by the five-angle sweep have all been authored into `areas/*.json` (checklist grew 84 → 170 items; the `api`/`datasource`/`mapping` coverage waivers were corrected). What remains here is what the sweep surfaced that is **not** a checklist item: product defects to @@ -64,5 +64,6 @@ these to the showcase would make them runnable: - `PENDING-GAPS.md` (the full deduped gap register that drove the authoring) can be deleted once you've reviewed §1–§2 above — it was scaffolding; this file is the durable residue. -- Nothing in this sweep has been committed. The checklist itself (`areas/*.json`, `coverage.json`, - `README.md`, `RUNNER.md`, `scripts/check-platform-checklist.mjs`) is ready for your review. +- The checklist itself (`areas/*.json`, `coverage.json`, `README.md`, `RUNNER.md`, + `scripts/check-platform-checklist.mjs`) ships in this branch; this file carries the + decisions that remain with the maintainer. From cbe280251313452797bad0c55326e8e501f93f7a Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Sat, 8 Aug 2026 06:29:42 +0000 Subject: [PATCH 10/14] =?UTF-8?q?docs(qa):=20=E9=A6=96=E6=AC=A1=E7=9C=9F?= =?UTF-8?q?=E5=AE=9E=E6=89=A7=E8=A1=8C=E8=AE=B0=E5=BD=95=20=E2=80=94=20pla?= =?UTF-8?q?tform-core.console-login=20(pass)=20+=20=E8=AF=81=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Demonstrates the runs/ format end-to-end: an opus runner drove the item in headless Chromium against an isolated showcase boot. Verdict pass across all 6 clauses + the wrong-password negative, each clause pinned to a server-truth oracle (sign-in/get-session status, on-the-wire 401 deny side) and a captured screenshot. runs/evidence/2026-08-08-console-login/ carries the 8 screenshots + 2 network traces. The record also shows the protocol earning its keep: the runner's automation self-check (RUNNER rule 2) caught a false "dead shell" P0 — a cookie-only clear left the console authed via its localStorage bearer token; only a full credential clear produced the real redirect-to-login. And it surfaced a genuine run-time precondition (the vendored /_console dist builds separately from the showcase workspace closure), now recorded in the env block rather than faked around. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YD9f6FYyMraUWYeJf53V43 --- .../runs/2026-08-08-console-login-demo.json | 70 ++++++++ docs/qa/platform-checklist/runs/README.md | 13 +- .../2026-08-08-console-login/after-reload.png | Bin 0 -> 73903 bytes .../2026-08-08-console-login/app-shell.png | Bin 0 -> 55350 bytes .../expired-session.png | Bin 0 -> 74142 bytes .../expiry-cookie-only.png | Bin 0 -> 74520 bytes .../expiry-full-clear.png | Bin 0 -> 44798 bytes .../expiry-probe.json | 132 +++++++++++++++ .../2026-08-08-console-login/login-form.png | Bin 0 -> 50149 bytes .../observations.json | 151 ++++++++++++++++++ .../2026-08-08-console-login/re-login.png | Bin 0 -> 74517 bytes .../wrong-password.png | Bin 0 -> 52191 bytes 12 files changed, 365 insertions(+), 1 deletion(-) create mode 100644 docs/qa/platform-checklist/runs/2026-08-08-console-login-demo.json create mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/after-reload.png create mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/app-shell.png create mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expired-session.png create mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-cookie-only.png create mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-full-clear.png create mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-probe.json create mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/login-form.png create mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/observations.json create mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/re-login.png create mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/wrong-password.png diff --git a/docs/qa/platform-checklist/runs/2026-08-08-console-login-demo.json b/docs/qa/platform-checklist/runs/2026-08-08-console-login-demo.json new file mode 100644 index 0000000000..7127fad427 --- /dev/null +++ b/docs/qa/platform-checklist/runs/2026-08-08-console-login-demo.json @@ -0,0 +1,70 @@ +{ + "run": "2026-08-08-console-login-demo", + "date": "2026-08-08", + "scope": "item:platform-core.console-login", + "app": "showcase", + "env": { + "framework": "10c4ea997cae3e0ef518f2f0a9bfbf23d456fe5d", + "objectuiPin": "7dfbeb704e1eace5dc5eae1d6168c4ded805a32b", + "consoleDist": "built in-run via `pnpm objectui:build` from objectui@7dfbeb70 (bundle canary 'import/jobs' present); the vendored @objectstack/console dist is not part of the showcase workspace closure, so the first boot warned 'Console dist not found' and 404'd /_console/ until this build + a server restart", + "port": 3477, + "db": "file:/tmp/qa-run/data.db", + "seededAdmin": "admin@objectos.ai / admin123 (--seed-admin on empty DB)" + }, + "runner": "demo run — opus CCR session", + "results": [ + { + "id": "platform-core.console-login", + "revision": 2, + "verdict": "pass", + "clauses": [ + { + "clause": 0, + "text": "login succeeds and lands in the console shell (nav + header rendered, not the login form)", + "oracle": "screenshot", + "verdict": "pass", + "evidence": "Typed the seeded admin creds into the React controlled inputs (native pressSequentially); inputValue verified verbatim (email=admin@objectos.ai, passwordLen=8) BEFORE submit — the automation-input trap ruled out, no auth-POST fallback needed. Submit → POST /api/v1/auth/sign-in/email 200; GET /api/v1/auth/get-session returns {user, session}. Post-login screenshot /tmp/qa-run/evidence/app-shell.png shows the authed shell (ObjectOS header, search, notifications, 'DA' avatar, 'Working late, Dev Admin', Your apps: Showcase+Setup) at /_console/home — login form gone. Pre-state: /tmp/qa-run/evidence/login-form.png." + }, + { + "clause": 1, + "text": "the session survives a reload — the first authed API call after reload returns 200 with no redirect back to login", + "oracle": "network", + "verdict": "pass", + "evidence": "After a full page reload the console's first authed requests all returned 200: GET /api/v1/meta/app?id=com.example.showcase 200, GET /api/v1/auth/get-session 200, GET /api/v1/meta/object 200, GET /api/v1/meta/view 200, GET /api/v1/auth/organization/list 200, GET /api/v1/auth/config 200. URL stayed /_console/home (no redirect to /login), login form absent. Trace in /tmp/qa-run/evidence/observations.json (afterReload.firstAuthedRequestsSeen); screenshot /tmp/qa-run/evidence/after-reload.png." + }, + { + "clause": 2, + "text": "an expired/cleared session is answered 401 by the SERVER on authed API calls — the deny side of the auth gate, proven on the wire", + "oracle": "network", + "verdict": "pass", + "evidence": "The server denies credential-less authed calls with 401 on the wire, proven three ways: (a) boot-time unauthenticated curl GET /api/v1/meta/app?id=com.example.showcase → 401 and GET /api/v1/data/showcase_task?$top=1 → 401; (b) in-page fetch after ctx.clearCookies() → meta/app 401, data 401 (observations.json afterCookieClear); (c) the wrong-password negative's post-attempt authed call → 401. GET /api/v1/auth/get-session returns 200 with a null body when unauthenticated (better-auth convention, not a leak). NOTE: the console persists its API credential as a localStorage bearer token (auth-session-token), sent as Authorization on its own calls, alongside the httpOnly better-auth.session_token cookie — so these wire 401s are the server refusing a request that carries no valid credential (the deny side). See clause 3 for the session-model disambiguation." + }, + { + "clause": 3, + "text": "the console reacts to expiry by returning the user to login (or an explicit re-auth prompt) — never a dead shell rendering stale data as if authed", + "oracle": "screenshot", + "verdict": "pass", + "evidence": "After a REAL credential clear (cookies + localStorage + sessionStorage), navigating to /_console/home redirects to /_console/login?redirect=%2Fhome and renders the login form — /tmp/qa-run/evidence/expiry-full-clear.png (get-session 200 null, then the login surface). AUTOMATION SELF-CHECK (RUNNER rule 2): the initial run cleared cookies ONLY, and the console stayed fully authed — its own API calls returned 200 (meta/app, meta/object, meta/view, data/sys_user_preference all 200) and the shell rendered populated data (/tmp/qa-run/evidence/expiry-cookie-only.png, and the original /tmp/qa-run/evidence/expired-session.png). That LOOKED like the negative's 'dead shell serving stale data' FAIL, but a focused re-probe (capturing the console's OWN request statuses) proved it was a genuinely-still-authed session, because the console authenticates via the localStorage bearer token, not the cleared cookie. Disambiguated → no product defect. Traps ruled out: stale-console-bundle (console built fresh from pinned objectui@7dfbeb70, not a vendored stale bundle), automation-input (typed creds accepted verbatim), shared-browser-tab (own port 3477 + own file DB, all fetches pinned to the absolute origin)." + }, + { + "clause": 4, + "text": "re-authentication after expiry restores a working session: the same authed calls return 200 again and the shell renders current data", + "oracle": "network", + "verdict": "pass", + "evidence": "From the post-expiry login form, signing in again → POST /api/v1/auth/sign-in/email 200; the console's authed calls then returned 200 (GET /api/v1/meta/app 200, /api/v1/meta/object 200, /api/v1/meta/view 200, /api/v1/data/sys_user_preference 200); URL back to /_console/home, login form gone. Trace in /tmp/qa-run/evidence/expiry-probe.json (reAuth, metaAppStatus 200); screenshot /tmp/qa-run/evidence/re-login.png shows the restored shell." + } + ], + "negative": [ + { + "text": "a wrong password is rejected with a visible, named error AND no session cookie is set — a silent no-op or an error-free bounce is a FAIL", + "verdict": "pass", + "evidence": "Fresh context, typed admin@objectos.ai + a wrong password → POST /api/v1/auth/sign-in/email 401; the UI renders a visible named error 'Invalid email or password. Please try again.' (red banner), the form stays on the login surface, NO session cookie is set (ctx.cookies() had no session/auth cookie), and a subsequent authed call without a session → 401. Screenshot /tmp/qa-run/evidence/wrong-password.png; trace observations.json (wrongPassword)." + } + ], + "issues": [], + "notes": "Item verdict pass — all 5 acceptance clauses + the wrong-password negative held, no product defect. Method: driven in a real headless Chromium via playwright-core@1.62.1 (launched with executablePath at /opt/pw-browsers/chromium-1194/chrome-linux/chrome since the pinned browser rev 1234 wasn't on disk). KEY OBSERVATION for a future item revision: the console is a bearer-token SPA (session token in localStorage 'auth-session-token' + active-org id, plus an httpOnly better-auth.session_token cookie); the item's step 5 ('expire the session: clear the auth cookies for the origin') does NOT actually expire this console — clearing cookies alone leaves it authed via the localStorage bearer, which manufactured a convincing false 'dead shell' reading on the first pass. Recommend the step read 'clear the session credential (cookies + localStorage auth-session-token)' so the expiry clauses test the real deny/redirect path. ENVIRONMENT: the vendored /_console/ SPA is NOT part of the showcase workspace build closure; it had to be built in-run with `pnpm objectui:build` (objectui sibling at the pinned SHA) and the server restarted before /_console/ served 200 — worth noting as a precondition for any browser-surface item." + } + ], + "notes": "Standalone demo of the RUNNER protocol against a single P0 item. Evidence PNGs under /tmp/qa-run/evidence/: login-form.png, app-shell.png, after-reload.png, wrong-password.png, expiry-full-clear.png, expiry-cookie-only.png (+ observations.json, expiry-probe.json traces). No issue filed (no failure). Rough wall-time split — environment setup ~30 min (pnpm install ~cached; framework dependency-closure build ~18 min dominated by packages/spec DTS ~2.4 min + downstream DTS; objectui console build ~8 min; boots + health ~15s; diagnosing the missing console dist a few min) vs the actual browser test ~4 min (two Playwright runs — the main driver + the expiry disambiguation probe — plus evidence review).", + "evidenceDir": "runs/evidence/2026-08-08-console-login/" +} \ No newline at end of file diff --git a/docs/qa/platform-checklist/runs/README.md b/docs/qa/platform-checklist/runs/README.md index 343324327d..bcadd638bb 100644 --- a/docs/qa/platform-checklist/runs/README.md +++ b/docs/qa/platform-checklist/runs/README.md @@ -5,4 +5,15 @@ One JSON file per executed checklist sweep, named `YYYY-MM-DD-<slug>.json`, The record shape and the verdict rules are defined in [../RUNNER.md](../RUNNER.md); verdicts are only meaningful next to the item `revision` they ran against. -Nothing here yet: the first sweep against the standing ledger lands the first record. +Each run record may carry an `evidenceDir` pointing at a committed folder of +screenshots and network traces (see the landed run below). + +## Landed runs + +- **`2026-08-08-console-login-demo.json`** — first real execution: `platform-core.console-login` + driven in headless Chromium against an isolated showcase boot. Verdict **pass** (6 clauses + + wrong-password negative). Screenshots + network traces under `evidence/2026-08-08-console-login/`. + Notable: the runner's automation self-check (RUNNER rule 2) caught a false "dead shell" P0 — a + cookie-only clear left the console authed via its localStorage bearer token; a full credential + clear produced the real redirect-to-login. Also surfaced a run-time precondition (the vendored + `/_console` dist must be built separately) now noted in the env block. diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/after-reload.png b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/after-reload.png new file mode 100644 index 0000000000000000000000000000000000000000..aacfdbee179e00c28a735b68e006b4f9bbaa69b9 GIT binary patch literal 73903 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 literal 0 HcmV?d00001 diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/app-shell.png b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/app-shell.png new file mode 100644 index 0000000000000000000000000000000000000000..ca781b3d4515b90f1f6b02dfa782f4c9136abea0 GIT binary patch literal 55350 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 z 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∈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 YTbh#&)~tL}2^3-QboFyt=akR{044--jQ{`u literal 0 HcmV?d00001 diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expired-session.png b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expired-session.png new file mode 100644 index 0000000000000000000000000000000000000000..a96aacc0c058b036b0978ed093e29dcaf4c70aa0 GIT binary patch literal 74142 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 literal 0 HcmV?d00001 diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-cookie-only.png b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-cookie-only.png new file mode 100644 index 0000000000000000000000000000000000000000..a2eb04cff7732b679d4b351f8fce683ee2147ed4 GIT binary patch literal 74520 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 yK> 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 literal 0 HcmV?d00001 diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-full-clear.png b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-full-clear.png new file mode 100644 index 0000000000000000000000000000000000000000..d82458c599b002f62cc87282e464b9d55771577a GIT binary patch literal 44798 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 literal 0 HcmV?d00001 diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-probe.json b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-probe.json new file mode 100644 index 0000000000..93e21fd1b4 --- /dev/null +++ b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-probe.json @@ -0,0 +1,132 @@ +{ + "sessionModel": { + "cookieNames": [ + "better-auth.session_token(httpOnly)" + ], + "localStorageKeys": [ + { + "k": "objectui-favorites:u:aLSv0AYgrsEQK1sX0b8hL3tumDOafC4d", + "v": "[]" + }, + { + "k": "flow-palette-recents:u:aLSv0AYgrsEQK1sX0b8hL3tumDOafC4d", + "v": "[]" + }, + { + "k": "objectui-recent-items:u:aLSv0AYgrsEQK1sX0b8hL3tumDOafC4d", + "v": "[]" + }, + { + "k": "auth-active-organization-id", + "v": "org_msjw4dxvzx6tujcw" + }, + { + "k": "auth-session-token", + "v": "dxGIhmvvC0Wc7H1VVfRz61NOptkNsg0a" + }, + { + "k": "objectui-offline-queue", + "v": "[]" + } + ], + "sessionStorageKeys": [ + "objectui:metadata:app" + ], + "loggedInUrl": "http://localhost:3477/_console/home" + }, + "afterCookieOnlyClear": { + "consoleApiStatuses": [ + { + "url": "/api/v1/meta/object", + "status": 200 + }, + { + "url": "/api/v1/meta/view", + "status": 200 + }, + { + "url": "/api/v1/auth/get-session", + "status": 200 + }, + { + "url": "/api/v1/auth/get-session", + "status": 200 + }, + { + "url": "/api/v1/meta/object", + "status": 200 + }, + { + "url": "/api/v1/meta/view", + "status": 200 + }, + { + "url": "/api/v1/meta/app", + "status": 200 + }, + { + "url": "/api/v1/data/sys_user_preference?top=1&filter=%5B%22and%22%2C%5B%22user_id%22%2C%22%3D%22%2C%22aLSv0AYgrsEQK1sX0b8hL3tumDOafC4d%22%5D%2C%5B%22key%22%2C%22%3D%22%2C%22ui.flow.palette.recents%22%5D%5D", + "status": 200 + }, + { + "url": "/api/v1/data/sys_user_preference?top=1&filter=%5B%22and%22%2C%5B%22user_id%22%2C%22%3D%22%2C%22aLSv0AYgrsEQK1sX0b8hL3tumDOafC4d%22%5D%2C%5B%22key%22%2C%22%3D%22%2C%22ui.recent%22%5D%5D", + "status": 200 + }, + { + "url": "/api/v1/data/sys_user_preference?top=1&filter=%5B%22and%22%2C%5B%22user_id%22%2C%22%3D%22%2C%22aLSv0AYgrsEQK1sX0b8hL3tumDOafC4d%22%5D%2C%5B%22key%22%2C%22%3D%22%2C%22ui.favorites%22%5D%5D", + "status": 200 + } + ], + "finalUrl": "http://localhost:3477/_console/home", + "loginFormShown": false + }, + "afterFullClear": { + "consoleApiStatuses": [ + { + "url": "/api/v1/auth/get-session", + "status": 200 + } + ], + "finalUrl": "http://localhost:3477/_console/login?redirect=%2Fhome", + "loginFormShown": true + }, + "reAuth": { + "consoleApiStatuses": [ + { + "url": "/api/v1/meta/object", + "status": 200 + }, + { + "url": "/api/v1/meta/view", + "status": 200 + }, + { + "url": "/api/v1/meta/app", + "status": 200 + }, + { + "url": "/api/v1/data/sys_user_preference?top=1&filter=%5B%22and%22%2C%5B%22user_id%22%2C%22%3D%22%2C%22aLSv0AYgrsEQK1sX0b8hL3tumDOafC4d%22%5D%2C%5B%22key%22%2C%22%3D%22%2C%22ui.flow.palette.recents%22%5D%5D", + "status": 200 + }, + { + "url": "/api/v1/data/sys_user_preference?top=1&filter=%5B%22and%22%2C%5B%22user_id%22%2C%22%3D%22%2C%22aLSv0AYgrsEQK1sX0b8hL3tumDOafC4d%22%5D%2C%5B%22key%22%2C%22%3D%22%2C%22ui.recent%22%5D%5D", + "status": 200 + }, + { + "url": "/api/v1/data/sys_user_preference?top=1&filter=%5B%22and%22%2C%5B%22user_id%22%2C%22%3D%22%2C%22aLSv0AYgrsEQK1sX0b8hL3tumDOafC4d%22%5D%2C%5B%22key%22%2C%22%3D%22%2C%22ui.favorites%22%5D%5D", + "status": 200 + }, + { + "url": "/api/v1/meta/object", + "status": 200 + }, + { + "url": "/api/v1/meta/view", + "status": 200 + } + ], + "metaAppStatus": 200, + "finalUrl": "http://localhost:3477/_console/home", + "loginFormGone": true + } +} \ No newline at end of file diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/login-form.png b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/login-form.png new file mode 100644 index 0000000000000000000000000000000000000000..7fc014c3c175dcb65e8b43fe0a67bb16298a998d GIT binary patch literal 50149 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 literal 0 HcmV?d00001 diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/observations.json b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/observations.json new file mode 100644 index 0000000000..54c3c21275 --- /dev/null +++ b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/observations.json @@ -0,0 +1,151 @@ +{ + "origin": "http://localhost:3477", + "appId": "com.example.showcase", + "steps": { + "loginFormRendered": { + "emailInput": true, + "passwordInput": true, + "url": "http://localhost:3477/_console/login?redirect=%2Fhome" + }, + "typedValuesAccepted": { + "email": "admin@objectos.ai", + "passwordLen": 8, + "ok": true + }, + "signIn": { + "method": "typed", + "status": 200 + }, + "postLogin": { + "loginFormGone": true, + "getSession": { + "status": 200, + "ok": true, + "keys": [ + "user", + "session" + ] + }, + "metaApp": { + "status": 200, + "ok": true, + "keys": [ + "type", + "items" + ] + }, + "dataRead": { + "status": 200, + "ok": true, + "keys": [ + "object", + "records", + "total", + "hasMore" + ] + }, + "shellUrl": "http://localhost:3477/_console/home" + }, + "afterReload": { + "metaApp": { + "status": 200, + "ok": true, + "keys": [ + "type", + "items" + ] + }, + "loginFormGone": true, + "url": "http://localhost:3477/_console/home", + "firstAuthedRequestsSeen": [ + { + "url": "/api/v1/meta/app?id=com.example.showcase", + "status": 200 + }, + { + "url": "/api/v1/auth/get-session", + "status": 200 + }, + { + "url": "/api/v1/auth/get-session", + "status": 200 + }, + { + "url": "/api/v1/auth/organization/list", + "status": 200 + }, + { + "url": "/api/v1/meta/object", + "status": 200 + }, + { + "url": "/api/v1/auth/config", + "status": 200 + }, + { + "url": "/api/v1/meta/view", + "status": 200 + }, + { + "url": "/api/v1/auth/organization/get-full-organization", + "status": 200 + } + ] + }, + "afterCookieClear": { + "metaApp": { + "status": 401, + "ok": false, + "keys": [ + "error", + "message" + ] + }, + "dataRead": { + "status": 401, + "ok": false, + "keys": [ + "error", + "message" + ] + }, + "getSession": { + "status": 200, + "ok": true, + "keys": null, + "body": null + } + }, + "expiryReaction": { + "loginFormShown": false, + "url": "http://localhost:3477/_console/home" + }, + "reAuth": { + "signInStatus": 200, + "metaApp": { + "status": 200, + "ok": true, + "keys": [ + "type", + "items" + ] + } + }, + "wrongPassword": { + "via": "typed", + "signInStatus": 401, + "errorVisibleInUI": true, + "sessionCookieSet": false, + "stillOnLoginForm": true, + "authedCallWithoutSession": { + "status": 401, + "ok": false, + "keys": [ + "error", + "message" + ] + }, + "errorSnippet": "Invalid email or password. Please try again." + } + } +} \ No newline at end of file diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/re-login.png b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/re-login.png new file mode 100644 index 0000000000000000000000000000000000000000..b4b926050b76d1b5108a2f63be54989b44fc55c6 GIT binary patch literal 74517 zcmeFY^<R`v^frznpi+W_^jqncmJS7^q&uY>q+x*-1(aBjTxv<_?p#)-yE|688x|JW z<yrOf`Thgn*Yn$R|FZWFXJ_s?Gjryg>$)aF<DDYmW9r9PSXhKgZ{KKPVc}q2?~gvb zkNJ^dP`-zS^#n`ljjWDe#{Lpc;L|M{ykik9U$<hd#U;!`RZ|T@b~oHDE?IPcyVtR_ zWQtGbf}gLz?yb&z3XFG}a4IVv7ls`bh7N2TdNi9HLboz6gVed=*4fEa|Fc+R%tEC6 zI(Zy2OsP}repPt}u5?Sqbc}f{v<N~Y#a<7rp6wE;T;Mb;<~4OT>CDjJpQ{ykK3{x` zX8dQD@(MdA_qrJUw$?sz#Xgn6D#|;2zMIu~>gj(qe};mE^)WJ^u9P}O!Te=uFo$5( z!;Y~^v0go5dziJwGHD@ZfPZY}ds6&HWl|h0%&^}(%i=b5HjarF4!_rX=1x<j=3=c! z_16r`Q84HOgUGVGUvHMe)_8RB=Q5~!b;#WLRMd2^F5Kv}aK=n70_pV6xJd1)+@A13 z1g=M^%|o3j-bX@u@>|-V$Vuj4dWcnB`eVk?e_!~Leg1il*y@yp>V7d@(QcHZs9eF6 zO>&4-o}rRvY4<oN^`9M<YDT;LWQw5|i<1yVFO*}wFa`A8P1!r1nuPaXmx-xjb%IZV zsg+EbQ*lGP>!hcjRc6ZDg}VIaHaw|+W2gVmj78q=t$mY~9cYf+$QO5-lGTan3;sM7 zcviG_Sg>x`y-kOYo3!hHZ1Z*AXZy4jSA1}`7Sm(IrEnavSpQVl;aWiM@7b|2Vu=0M zPKMj!mBi#jR-6{spzm&vuIGu*dZI3dY_C960y>69tvy^J6;j1<_i!=RMG@Bg%{m+d z0}1`sHMfDO_Z<j}LPF(o47kW5-q_k&RvWzOC{rDus699+Pj%p&QW&KoJ#}LDB$(9Z zsa~p(yW>vm6!YvaD~l@`PJi|MuNRAiYHB?_J<lH84|&TZC?Zl@Q}g8UhXy>MSd)ru z6;Uq|R#sMUrjbF4v0<u!pU)Lkn@>-U(=@17z|{BAru0<Yy=lWn!>!z@agW($jh_`y zG~ed_Cu|y*YAzU7p89JcQ^-R%+Oh1}w9KJJ-=7g@v*g-8mz0cJH8VEm#MS5+3!};T zHV%7nSo|ipt>(3ZYCnNMkz60C&vPR$CKGp&%q4Xl+<1^Fx!Tg)M;SvA=}n8Amo<eP zgaM^Wc6M71ot@Vdg#s^QMl9i3Pb(jiuD>;m;OcUAr9tK%^83>K@f()VL*{-hEq)hg zt}aUw>>|*2EkoOa&bhgFOdMVBVmL<2%4k;1K3NR&oPb`vo~T1u-^C<ry><Q_Bd-cP z@2`9MOAOlr1Yj(7Q1w3!u|T~05WvGp4#01)O!V@5T}S?BFBDO+U8cv!ul)YVggY5r zTx?kKY<5=S<$VjiYtR!&x7i}66ry+bdI*KR0VD<k7AQl%&6B53U6B{oTUl+WkSfsw z$L6{H>R;Qr4(b=mn`e<Eb4TVg2Q3<-T}4tu{#)+s+wf@Vmkk5S<<MGBg=?@qc^K;T z=1EETGfeL(SdqcyWrJ}yININ`c!!3De7))fJg%dooyEnqi9cSrJb<j+mxn>X>C7qy z#Z3n672MzaXW6b!%4PQ6DTs;Zb2KR9-nTfgDeiG@VGMA!^XKg0rF)(}r)*}KV5R+< zhKZNgoF$>!QJsq-JvHKF2{YtL*SWc7t>Vf4{!jNL@DmC9M@B}5lC(U&CSlLGq&GJ= zb-FGv#hVBS2r%As%HgI^HIg(n9Vr!LkC9f_slvY-^d7cn99fymVU?W}1YlbnxE6SK zPp?~tT6A9XR|saMemlr`v1i=4h+iVlc9@U<u1ul2>LsVHz@tCzmi?~fZ)@8CA{65f zv-_ND-txf8mL=D7*kfAun{txfJQB(8v=`sXGcr6}TVF3&@Ivsuh3&aQZ(L0PEJ^Hg zV9UAdmsww<o@v@>#lv!em(veD!c>8+?}LXN+NCSMDn|zcaM<n$2Eetj%G}uc7yPNY z-SV)|>1KuXQq1792{igUYLmdM`P+?h0p@wkqaOASNF?(myi|kD&1a}2^3988s=zO3 z`;WTR*P`%d8ad#|uCHWJD_e75R;(@1RsUzEg>~alnV$vcB$65!42nB3bawl?yvM9( ziLe!+xZx2ORYzX>#hn%>8Nk>3<t6(pvS0KL3|I+it3@whzTw9+vfyWMnkO=0dC}Y( z!G>Kq`5PNafrlIv_-CgjFO&!S`jE#7l}ka?z6U}nXo<k|z*D3zIX$KGLUk)3D`z<K zLfmIh9Vj>o9jOY!i>c!9;+3u9t-DXe()v2Ij;$c6c&dVE$P1@Ku3NA*K-hUEC}h}y zj%)S{)bRWTV&X}|8PD0BEV6Mg^VDJT1wxj0<H5YWCgAnJRH)b1AxijN`#B{ERX%E0 z{3uA7x88Z?vo65jzd15(5k;94Lx7n^N4kEpB;hj(dh*u0&aR|%1cRt2Gs59TsqpG* zg49*8=x1tqjwG-Tao|xpuqgy6!tl!Dx}>|bPOz8+Br5+RPF0V5s}bKEc)7mgnLbnb zHNgEdTDZbA_-gwktdiG~oQa*?e|1t+V5@DhN%OPr0FW5Q@CGyDDp^x%>9uCcgamDa zjXW8<{^`d~LZU9|25{3jf-)?TAlZi%l?}BG+`e9Amv;fxDZX=f(7D~c!801fI6XyD z!EN5XfR;_M8*{gf2ZE__`0FZaY5=FP9teQo{M2pt>1>Vh(etd}6X=`xax_;NzxVch zmfW@o99bn;+G%OEMghHZC_Kc*OiumbC}4MR3x+<_$G)KmYZn84$pW5vT1*U|&lKs# zKAOms_F3fuslI!+p)d~rxRM}!wQd~2q-Z695^|n{D7;HMk%QWru0D-#!Z}>@{APA- z(^r$2C`kQG+Pl4-N*cYYNKMr44Gst10hkHzuJ+)9w4{h{;nG*<;vn55<@_X+DP}5U zrV^BG<Z`N&(i7_jZT2or2#LHsefFo@!^-O~a<uh@52cdR?R0GT9&4~WqkrGnWZks{ zbW~Wy=B_>a$m2j|2#7Upx|2!8it>-I+S*3W7AjFs&`MJ>fd}x;ok#jZeE`LE<2M{) z-kZ0FP`pMrBS~uwAZ1d{=Nq?$rhq*}&=#-Bd-4?|c)5w%%L-lr8n{Ed3>Lkor*}P9 z#OAz123;b&?S@VLRyIToHvlPva`BNkWoPqzUEqG{zCx~B?gme4rz2D6WKFfi%3w*E zp7BFw4oZ6_P{5dFcT~8Lc<;Cn|5dSV_7dmEAfb=kt^Lc=o$+-gUeZIBZgdvr5bG?2 z?mM67kq|$fU-yx0l?!giDNRj&7b%H}i8LZ^NAtCJohzm>F+_ve4DF~yv_Tlhhoo(= zuzz=(ldMRyeh0n8cbF-FNR?K)e{N1`i{F6)`{<SJx&@`()<Nr~H0&7ixplg=h+@^W z$8)eZM==I6Uu9#L&`nsNPSvBPy2ctq^HWFKD1cTldnCz8gqT6HPtE<jlPZ8#qN)+m zYCkwzBA-xCGdpDLYusQTpiPcTDS^w$U@YvG3w@BP8nayWXHQWzn`m14%F>GEn?Nsq z_ugB&k7}D_Y^`(h;oC-3bl9;H9a;JLh{atw9aMB*+?r(yb@z|2x*AyP`7jHIhf*JO zHfN(H7mlO?D?S9l1i+sQqjwtv27Veo#r3Szx+SHi4-$H{4njUPFdTDryCQ?i(Mo?P z@Ghk|Jgm5l^WODquwP!EjnFo2^|IJFJ4(C2BW94U0g5*k@xqV}NZ8$&ZaLU2<z_gY z_i}II3~mF%40Ho_k#4IB>ExJ5WuyBNSERFfmseBZz98lt;N*k$pAMpGrPV&(t<&S; zOILIJi7qTHKuodzy5>nU{}xh|OdbxEE09K&3Ovuq(<S*WTT{b*dW+bv4K53biZQfb z-$KoDnkFWMu_U$0w1-u4ugVR@N*lcCyA~H2hPGMoND>kPu7+>2+_S!>pDv@SLOQy& zyX#d(WbH;w*A*OfLJ9^C01SceluXmg)OwZ^zvuHUs!&m-W(6T7hg5jSxH=ToV<IB* zLs~E8wx{w3^h-I7z)i};9gI!emtWkZqk^AhB#}a%(VSi~NE;d(?%bsCr{*l~RQ<$8 zH*Qajb>L~|UQI)87T@}c%!rr=p_3@LM&0)la`*BC|GbaG2Y4kE)fEYJ%>+aP_XV{Z z%11=KujNny-sysl*(e@rfd<F1L~>l{Oa-3r?r8X@K(WW}Rz?l>TJ!3E@{{_7{5@Y2 z$Ubt6x6pH8lVt3JX7x$9o%&nlzTU)WhUwOrFQo~eO&bwI=^`ZqP%<y^^>pd!_M3}w z;Pg>nzn)%TLf?isF$B4J>3tN`ni${RSG-eJ6@C`;xe#bxkAe^3*{4B!hJhywyCTwe zYpv2EZ#_Jk^C2*wn~AKud}(S64S{->G54dfsIlr3Kx}4gz^&I!$V7SCv_4FkQd@Xm z%+=MFg(AZ6!sljqDZm@Mu17nV^J<z-&lPspyxN?q-MsC)6kaZUdwXNY0J}rb;A3Lu z7CtzUYEuf@GLj*D5)0F-Qubk$b<5=9)=zx_Y5jcAR2t1DilA^dU_@L?ZEp2m<L;*H zIhJXdqiljgT&g)n#g1~mYgg!LANTBV9V^<T`Fd$nT;qzriURQv#Mpn#9Ph0jnia=0 zTw+Li3T~1SihQS@A*P7^8t)Q#d7#f`$ng8<a%m>3&S5EDLF-)-+}>w;5gZ?{wf(~8 zxV+Vhb?7mWthl<at*z}jBP$QmbFSm~ZA-!FD?}dyu=1IRzc6>G8MTDvuS;p}9RGuT z=X7C*H%I9Z?i{*b-3oJ%#>z?&!U=OT*Wid!CNO2-_p7Tno}M<8OL}_K0tN5BJBeOP zIYfZLnVphQAI211+WjckJ~byllX`w`-Jcv=*Fy^?YMURlH^;O^^ufb)3ubMBAmPm& zPs-Yw+AqXAg=gG?w%=dPjg4jgT2dq#D=yv<D2Oiff6WjICup~_a#<>ph4KgfDRhgv z=-)^nPCO=%q#)ok5=;zXRRk#CR?G+f_UUCrADDIIS^Cn6iFn9d#+s!o4H#B`-~}Zr z*-YytpA`@QJ<&{Y8cnF*RIZkh9^Bkq6vOm4i=EE_F&QxLqp><ZY~b~FKGoXe63xxc z4awm6<N3u$d3fG%?<*4pJ>k#Py(0@&r@d0knH3#3zWF`XuLBxf^|GXY{+i3fq`t(E zmcf$71>YkP*F_CLU|u-k;UeMt4aT^)hhIWts6`V2jxH`21F_~qX;M2E`eydibtlCW zTg@koiUiF;TfxD>8$OOS=ik>I*a%#AG+#>85~_^Y(0rO3`_lj3<-HPf<GoGa-7@;= zvD`czP=r!;LXGR$$X3e8rSqzSO;x)UH9p|8q@T8^j5@1}wRgLfbfyiQ&=P%7DfR7t zv4EI-1O5%2<`ANTg;PEC(fou{Y6U$#AFn~Hi>nhW1=Ci){r!j_%jy)fYnMJPP`=B2 zOTwygz(se`(;lM3w%{EmP@CV*ZpiDNZDwV+@d<siGzOoo{-iTS2T?OKdVsI%@#R_m z$!ole!uQuIYAT!`lI($W<?|!KSN;)S+^p~#Th>^m8}EK=7BI*U;hI4%$Bmb+V_K*J z8`VlTmmYJ<X|&C^1!WzyBGj|^U5`KJipei~OThhXq)=)aQ+6!pB|03E9=C%ey7YeQ zRkC7md4NKIc)x9yglFc9<)7F$8WMqrzmi%nUmG_%R@8ETTlp7zjODb_pb*e7c;C7F zY5WAii0gY-#Pr!y2pt`p%MIH1?zYyI7(65C$=;+`m^?&{J3DVcxU+<Gu;c)Vh=|b4 z)_zWp`rI1D7$!sPH|Bjuf6x?yKXa%`JC@Er?p2~-XcJkHTB2}S6tV=Nbv71p`n|JD zd&U$ZOZdhRj>%<~0Z%RlPPxyUybca&eCdnK$s<B}-Vq6O6MYZL76W#XiFbB(VooiC zn|xa}8U+P;a4oNWg_dIv0%v6{MYCgbFpVRjPuO+YACuN14vak4jO*>+iMD0$i5S|n zJT>?DMg{)4J4;ER?wYf2Xjoy9?pDpG=Dn!D?fNOZlfKLgw}D)P^T+Tf=%V4Ab*kJV zcViMz@YYtuW@`OpZ;nuRV^N8F)_BPPj{&ijuf=7kp~b~xA^&tROB_N^BjWh73g{Lo z?DoKE+M(H(H?jEI(Q&*6A1D#lo|>AfI>+47Rmgxj(!_c4j5l?S8UuD#@>r+c7Jb%d zm6W0Mue=h`!Pn?0oHM&U!?IDC^QLVO*r?u_g*0Nm-a$5Yt_1Otkmf=ANk2Y}%dqAB z*|5@@qL7o8`_9+WzQL@Z-t6|QAk<k-9%}{8lH+on=0{lNu<uxAwfBH6>pO^f;|JsP zX>R-4I=2bOj!imMb(Gzq_Df8_c&SSk!&1204zn=|U<u1EV;YV@tc4@2OZo@XFCmL< zmz|$>@<`*g-If7OcYb3^OgKM?5Dl$FS%mmN+N{Ju$b$hLp)t08p{JZ9ZtUb4yh#B~ zVoq&gN%^8UBa2nd&2LS4sU4HEjdQHNoX>tZ-fv^?&~p=g_1^D@8=zj2@MDqZy^D<~ zVD-0dIx$y(LrJrtcNspa2hJH71O<5Q<)XAk4o%r<F1N;k?8XEAkx?cd9v*alC>Cym ztLX+h035JYk6G0xtffTWqCs>9WhL$IuPLXeVxfSyqHTOBC@DFrcNHze9HB9$IdziC z@MtSZaCKu#Ew^@}PzDgzi*6q*aMf#Hx9?lt>!a|oT}Jd9zwkEgca`MsipNCyPQnMK zu%E_~UCsFYjM=rdPY?KJ9#`t7q*E^1^$iY2?~EwSFDyW>J*t<!@&waL=ct0TqU{_U z`ud^Di@hPJn^6W+%N!NnGxI>y&kn^eL<}>OSQExHvs=`{MKk%@;-I+1B)Ul$xV-!# zTD0=^c)+|AjSQn<$okgwy^8eCQ=kyD)mocdybdPOS4uWEJ4o!pT>#|)Rl0~SZp%t+ zE^aaZpqF>#-fI;*VuP}(ORhijs)h-|zH*G$zB3XIeFI>ruU`*FTO!6(EHe*zB*p4x zKfLw7!^aa(ccnA(a#)C#{@GmG$Y`}%5WTy#vzd{~jr2I71~I$Gxd&=-Y=EL*S`{?z zazbN;#tTbdM!cQi8R0iRPKskkA2a1q@<BJ~vqc$G^Gag#^uCh(Z=+R}?I`~Z>8R%4 z^4Rjs=E2u1_`EPaFZa9fmU(*WJePEeb3ewb>Fj$!Q;hJSo2g2vn;%QiqWb&bSI$c% zIHjsXi{zBKDpwaEXRXo3vqePz$MgL&d0sV_d@3vvZ|!H=I7Wc9Pgp6X<mI(9T!a)a z=*oTaCT6$2`&&lceC@K%ZQumfuuMD49RBm6xSFJyS#>)@zsLBax_5?Ks1Q+M;pqgH z==nOEnarRsu$vEk`&0?cqVdvqX8zh2%$NfxRr=&4!7fe6B{43}ADvrM0gSR$6H1=0 zE3Wa84G6$JP)O9^HaOdU7MZb@$+6}U@utRR;qcL#0>2I59DojP_wi|%p6>r_+wjKn zmLn`&@^)p5*-C-%Xbu`xUJhz)<)phz-7Q$S#vq3fPRG6J=GQk3<?phf?k64^9oPMv zE3sB<!G<-_0Ppduv4#@i!OytoQofpVgF)Nb!PgY|l2$i9I1a~p+-cRnT&u6J$uHP3 z^PWwyz1YCsm9IErb}&;8X&E;$Gegh7D(mZBx;^=A<%js5c#bdIC|>8dy1V{_f+T3O zzdQi#Z6A0U!`*lwO}AaRPZKq)e-s)n;zq`nptO`hhiY4L_@uIRxNBko_E96wYL~DZ zf7y@jl2Y^XsG7K(j$I>t-H~_Wr8`XqiU9$ioEQL}m5{e^@mnp*xXw1lq_^B{QLkh2 zz5u_b=H})MDS+A51Ml6Y))uil)Cf%a?wFbLDJSP177#vl=kgoxM+HwBl!Dh3;r99Z zqU`)~#`rn)s~y6R&1Ttlb(%gtLOxKB!O7(P+O_?OHJqmeu0Ifi7G@3mN-S<6Jj|@F zLf1f^XY6t7NgNvs_7Sf*%4a7fv1g0@_oJTn+x{pP?N@+#&R5Hq1cPDHdC|cWo~gCu zfIOO;Dv>=ju{fo!b4cRvc4Y1{%Zz%a#KE*(??hpm_vMa?7{HPt3@q+@lrHRaPdndd zq27V~hKYwsXAbN4c{TK4z9C=>s(xqEl51umb6hXr@9z)Ai_U$Ulpf#Eu~9mMfo&c7 zDTdO|8Tga6>R-ea*<B9%BavIf=0wn-xrGI%L;OmY#Wqu+c6?^PnX=p3PTkxAQ5W%x zDKwXpK1yD*dN@{T2cD7Pp?gwE#Kk=n_W*;O7TK(>X+tucK^d<^g@QH;P+G-<@Se|d zWRVUG3f)G=M%~OTYP8FRo+n%t;pc<<pw^gtJ6mR^e9_qjL6;{lzpJz2>w0f2JYT_6 zH;YkM_4f7l`cMOOUDXgXHg5Asf6Mm$L1irXCQd*fcy-(r+L_2)>JwORPv0n$YSMmp z)#davr%shve@lNWT1{nSUH!Oc_rP>s$Zer*B`N)qD5L52WUtnKJuF*X@tye;eYhPn zGxNmIP>60(XsDnS!ZE<f_c9*vQdxbZp|R06zwYig$zwq_Q9BFj$Hr{(5*PqNPY=jz zd@-FHi#S{yE!6jQ9i%kBdmGDuI&wMOSw?+fQA=-JpK;q7OzZB?=>%1sG#ocWy2L(Z z&ctxj<k4nF?<5dV(Dt;8cJXvo$zIGIQVkvfHc(zua%OgSOBUvtT-3D=pPa4p#&s7q zf0@}}mGLRB6?gYW7<U5X1Tj3oH9HMT3w?AnuzxerH9gf4&b`}bw%hsLV?)I}W6~s+ z1Von3`mBXq%fq8zACC4o{MFT4^epb}{?V3X6);QQjr+R_^?8XH)^dZxlwww}=TR`5 zjW0ZS&4y=k{r>Oki{0h6lCt%Tv^2y-C6Hd!=f$n2wX3UHmf6zv`Jt%Yv`+uI*6!67 z94+B@HQRCgFfwu{pYV>m@MOmAfqI(M(TSB!qYW9cwvNdvhZ57=T{+wXv6!!I$HT<J za(x{j;xOsHyZ+&6H;Dh3j~%OG49Zr@@hx2d6EBKPNuN#}?3YDXZZRkHshhURQq2rH zN8~|WKE|?kLG5_(j$ZlMp7yrWsc4f>f0S&+GT_m-wtj13dSIy3Pl2VMI<{u_0N!4^ zUkSgy*vUB=A6qNjY(Ea@L6kQ-UxAa<BWRv0{R#}`w<2eKui0L%cbp^eamvr=3D%zK z{k@F4%Yj^9qP^Zz&CTm6p8fiY!ETe(i~M6qOv2HHHLs7>GVE%`v%le|LrVaafy5gz zn5oT@w9oZErO*C6yxgg$<EEi{S1m_leSMvS!%0O)IIO;&nR*H{v`MMr;#7+M&AJzQ zQj`@-5kYC47awpe+U&@&RPt*U7XuZ0;-7YrlVVjG)W4ew)&6jE7}P&f9hs1rc)QB! zb|mg@-T%G1;|tO5e6y9S>mdg1LF|h+`R@&DGUDDfUnwf~oVvcZ68=?2w>sp@!D9Ao z;Z3z`MhtDqFjn#%=^BP~DtUbVQ6_abomJCaWp0UKgoAOpv6WN1ok`f#ANU)_!xu66 z<31<zW}eP*`ca_Q<{}RD;e7McQUr$TX>O)M4H^)B??=73#&BdsVV#zI^7l80KVnd# zZY*UC%CFZ;UsPaZ)K`wM=1+aA;~k`^oicBiUBj7M3A_5L%SK7LHMFOHxC}VH$)a$Z zYcS@}#kD<eb)Bl2EASp*XR_7ZSU`{e`SeG(Ycd;!Y`V`%iK2xTu$*|q29!<}W9I*p zx~&Rr+$GNrlpufaR}#M`4W8TJlnuh=D5bsJnF{Iv4(#toyR@D<kMWjwcm43vHDD^K zdQFP)z7D6TsQ<a0l~o^BCeA#HO;aZXV4<f+Brq$<%_^X6X=CHyVCeBfPg6I$gFe*Q zDCOq*x=Q(VHE#Z+ok^%>$2IwaZ)_aRd&Ikwf`tvWbgH*^`~{3DQxCnP=CPc%qNX~i zH9x$4u~#>!+RD2<Uh||{oL2>U;N(~iSC%stmzGxND8F|QvZIxO$-l9R&HoUgDl7?h z*4B(z;db&o`Wn5Ct~@+I-M|VxPqWNK#1?6Ky`9a*{DLvnQ)az0;UBsky`ca4>@Fz( z5HhKv#?^S0o5sZzD%(zbrF7$ti2Fn4x$OimIhUi(fj@syA`(S*E|2Vy>zn+LII&UN zxPc9wbvCkoMLn0VZ{$K`-o`$FQ)9k@y?3}k<qWB(M?vu0F#FjnUUjOb<g>DrVGH(1 zVS%UMXDDT+X3Of?`T3$C`g<=0Tp8Uzm^+7OqgVN-xhe4tdpWgujl+gzz*hfZn^Zlq zbtk`&e<fhtm#2ke4q*o@-$u3fZ-e=k2opn!uXQUneaU}`VAhYt<Fc|rZS6CT20E6H zCv0Z*b%K>OG%@R218nOBoKoFcd#hzBJ97`aTFmtb0SlZjmcKRJ7$-8ZoAQG^U^A5K ztqX~5^@NRt6r;?Npw8-mjY*^>C`nJfnOF6tFZh)tI4Kf}V=QDO_WhpEQ_KSRm=>}c z^8LG)q9P5h#zKC9ZF9>!essm&SB%S03g6aiBaGiDc7TQu7sQEfng7*tILDQnA`L<2 zD~7EV)u+smdoL%lQl_3UV3;{+wq*v}eKcQP$m+~bE%@WZK3Yr<SR7zg7WH?-Rn^rm z1xi~gKHMuI-3YF&VHzHe$>04=e(xpco<dU=o1bgpl|n|2gAt%6Det@5BH8t6%eB$u zE`c-teT#183JI6l)VDu(-mWC|d}=EFdGx<nfMW5dS%Ty~Cp~AU;WlUTkauN4bn=y0 z{=`70;6HesBU3+iVZP1dFgeBrh5M8Lkt*>VtPl|i-<RDf*?fUzyRq=FSoG7`*ryKS zFyFPL=X@ZC{Sy1lif1O~%X5nF-88!obG>|Qx>72znmR>ZN`O|<IsUXO14>RxO2`^n zQckpdA6Md}nsF>U^m?U2wP4>TxfK=J+-x{kvHu^IEqMz`G%>3RO^e69Dg$#)^Mmdm z-IWyq*W<%Pf7~;b0Rl0H5o#54@v5hR!C6_n+S&O36@{=(IN2)byT8FKK|zfnK$Z{= z4rVh0@ffXa?YbBZLH_uM*v^B5iFK4NSGpi}stJHL%b)fzRRxLIYdP;hd(|KI)-ojm z!{nz~p65$=2G~IPdU&)nsGW^+>>pkKQHhV1-Z3;`{-hv;*<Q75X^fnN-z=k?cY3P% zKk8ySPcTP;yv_ee4-&LWUTTOhudiy@dadd)nsQC{3hlK5`G9Q!6pZlr6TNrh(toHZ z%)=u0hu*K{1kKfRGSV0trr~z(Zgqdj!}XZ9X^r|5credLqlg?E2khBg;UCoAdG(CW z!5*{jzl`kOJ=2B!aWICL!wMQJ;;Vh=brF$Y{o@nIC~orbh$3h7VW<({esH)^D~Hfu zJFJw42j4WzXY{%|Gv`A}R$NOuaV?Dw^0(WJ;}KlfMz_0dDpkXm|1=CAsnzT{Szr1U znu83+WhVQw$~t9qQSk-<7v*SaA=jbKXk!(0KRW@!%+xZQ_+R@%WYYA488K5|om2I0 zol_7G<bp^l<uv79a*e{@gZcJC*eL%R{&+z3l$iRV0HH6H%d21Hd!+*0qb+<|7(qt( zI`_Y(bZ8kTx@d#cT&JqbUt!q$e<Q`3^!c>EZ}OL8$HLNBi1;6$e|#i^^}jy7ckd4r zfO%3LJoxeV1<Qi;3D!UL!$+n6XZZimJv4i_VFTD-KwGg&AY#fW(EP`k!w{`v9hBEd z%tcUux82kNvGg5!4h%2NLhO#Yz$*v|WME{E&d_ri(wmd}w?xa2^^p7R!r+r-yRG%x zkc*Plpg*&lZCiYfm2zMDsAULKTufO7qjpODM}|gI_DAE^)>wu%W09VH!~%tC<0;OU z%b&IFq$-0sNFywNxy{YZ@fV4<Bhe!0iQ<Hws~cpsV;<bG7N74v`dQq^Ipdeco&h?q z469{W<*oGPRfzqwu)<`1I2P@xKUmUBbBI2#drxdWP}<D!2X)5!QKd}$kzH}VI%wf( z;c!WhNlT!bI97Y$Mp}0Yr~aop?B6Qj%G0a{hb7W6H_+Ksqx4CZ&)$5F5zdc!w)&L# zyjv?(D(Xzo>isvGrius;#X0-~>le>UKTPB98SFH1`CR2#Xaudl`#Wf#l1PEeVaWVR zR;DD-5Rj$<{w&yS(iW%&;0MJ)#2aj|Mtbvf5Qz7hcS5!&hGn*KeRIjJFCe_-FlNiY ztGixDx9;BIY>ojIEgjuca=L@9y;6F{B;`tz)(XBE=uM82QXdb`@GhUIsHlGB(#xuK zN2ie(@5l0*dgb!91q6;!DzP{aa4JI<gz|T<W8`Pz(1^Jf^Le@#gS|FB6lJz^lFV6I z4Xy_fOu~OU+}0q!=e`wl@P`a%(T)`bu5d_W6;vg|-x?e&h7B-&A=Z<Iy6<oRpMO^= zM3k{?kCo$pPV4K6ET>OU$NrG|3GnFCO~j|Q*icf3YKPUD+2e~%%t8<~s-b?r#0}Mz z_OMB$r5JW#v=dU!YWeE-*G<;CBv=b`a}6#Nr~Om8w7i7snXeR3=ln<c3WR%KLZ#R; zxV|!AIq4+0&SqzV!G+y`15Bpa*;kwRKIUt5Ol0u>#t!}OqphQ@g=(vX9c`9}buE`; zGmU4}PYXqy=Y3G!<zH4vh=>qz??nuh(1z95L%ve0v8lo~w{Y0q8#l*nN_)_7GQj7k z1$e2TOnEftYd_FnD&o5QPD#yWp~dD4qTJkn$6rwq58_7m^z7;qmW&5)@nyV9PRP^V z(;MoJrWW=&chAw7t%ZNZz_?S|!Kg09%65sYq=m4k^Nq#muU`YmNaDVwWMpKdr6k%r zI8YgYg&WkDuwW-vib8JFDbfR_Zf!7+3$X)vLORjZyF{Pp4It$nGpFGGN?JY54t_pN zkt9w}PmgdH4;S~!cX!gYdotpa@?lM%YVS401AUz5dVv%-+f6B!Z$)`GAFIE|h<<{k zWeT97WRHwJ49VizzP=@YlmE!$d%tVlc4c$xYW*<3u~}tb?1W(l^XBc_L-a2R>GC59 z!(X>R_UWAuqxeXWDF}q6?KvA#WqHpX-?yv$DtD;qq@mXxEATeXVn~g|l<1)vsoz5F zu+(X@i?wN>ia`i*Y*cW&h7PPHuW@yK|MuJjF(O2g4>@9axP2<iNsO#ZKCM=Jyll#0 z$61N*yG*$hK{@(z!y9ll0N;NhFjG?Cl1;hIs~)voI_75{_Q?gtY64X8J1hP+os<71 zH?Q#)Qo+y4`S*~hHy~faE!O_haEo>IE8lYHBhu8EJJ12mSqU2;bgH|8`t2y6*ie+- z(Nd9PigI-#dZ}&(Dl}U>+Wt3OXm6%$sqSjaEltq1mmv4|8I)M+231L3Ca8yMlx4gQ za^0Nlf(2@aA4f~|?)z6Bi_Bn9w(TR>g|R*lsvOWbOg#Z__XO^LH>|E!Zz|l`p{fhT z(Dk=1);sgM!0P^!K1%plJvIh2?N(P--kiSeF-Je+a~@@7;Bj0my2n|HSqn$OCT&-- zHW;jFH_pLf=fel}kql^6z(Kb+O#>Qr6YGtAYe??V_+CZvEjF#Zc?J)~7tQ}JF%qsQ z@9GHs2alx1nWu}~RL59Q2aj{M-<^+qy$ue*$)UzckVKIezKBAk*8j}CyBH-XVVBv! z1Gq?#cPqlI2k74OxW$qApv4U4-PAN=N4Ihx@a+utm|gDQEs?c1sh1I<*-wepNnD$l zZ?g;cyAb?()L;gD7FTvhSqNmrQXmwM{^3@A5-<IW{1YpYR2)h6d1}@d4_IHkA*-*T z<IMk-lr)t`CTx>#cs!BIoE1xv5lc~yPodcW^Tmq)E8MHsBlk96D7LC@>Hpby$iiA! zlx4P#^J8QvBX`(SD=kj$Ki3rgBFad-A9UgrI6ugu>yP+l7_X;fRQ49FMu(Sfkc{=% zlj>4;4RvEj;c5%!{wPUw3Wk*6MP4lMzkx+4Ddlw!{Z@oIlCAO>TnS5)G5H>b6HrT; z(V<S4n*5y>zt8RL?CewJFd-7D&V=-4{mA-!j|8Im*Kw2G4{!5{Oz}+GypnXA<(I4z zUv0$pB~-sAjzVR#I8(qpWXdY*x&7n&6r5k)gn=m`V<iRz%?A}R=4|p+w}pxPHu=>R zej@r-)C27P#yu7G1VJTJB3`~>^2k|AMC67MNv>Ryy=he=8RaBtEzX+o&Y`rk|2KEH zBKK>A<yhtXXX~F)fPvr*9`<xXGpFI$;3?9YxkXc3QLTRqUzZ%`2Q<KI>RI0K;c_d( z^|`}+k;Pho{*)EvK_6SjggxtG^GmGcw@kdShOhL<%9iG4pPj{@3#~-J@2V_-H3;!j zQqn1_t03(UZ15sJg}jyOa2Ig|m%$YUP!tol?s|@->})(P&3Zq7i9Lr>lTgVQJEK#G zrIC^Rxwm``4Z;wy2K;0FWdk;*^N^7Iqyz&l*M)+qs^{3YYFW44rTAvXR}zGH)7;6? zgn%dLrC6Mc)V-<F1q>$Uz2qij^iU-8#z_MvC4xo~!<raKyR&MHXWpIjU@`y?UtitW z?<!1{Fchq<O+v5!jU2*+W{7Uhz;BE=(y}Y4%ZYl%+gjVWmmNyAt>avwt{%x&v=+`z z>;2nX#+z3&J2>Ku>2>NWUFrHK)$BtmalZMX+&~}xoKz1)#84#nI?RIZp1UK_&F>W3 z^&RJYRawuyHr6__o~K3$OcBfab_Y)=mtB>=?0xvO^xQ_mM^csK%^r;H4VWp}OkR5S z&oPNvy}BVjK0d1=hc+dSBN+PLu4_-!S8Hjy$m)wv>m@x`23L{qh-*n-q!1S3*j-!S z7JVLd#5TGLyGF7e!I1M&i^m1r;^uxxHSeb;k-_HvYfz9ASOCRVm-k<Te{BH<lb>;0 z6!X6>YiB2>6P3CQo4F!6#l(()t)Z~8z~ul`zM%jMhHJ?b^*zbghbyV7as>2ruu=>H zV`!uT&pYF+SdjuT&xGx#PT|~Ki7UMrqStoVUumsHMn3hW>w37*-SN$~-h@3qbQcC< z09Jj)W%I!ZE2_MCd}iRh(txbi<DVFIcWlZ@q$DdXHT7VrI`h88A-heRVoZ7kQLG!h z`3{2tLSfrNdi7~74rY>?c%_;S`_R0uU=xOmMCgoZ_tc|o+O_BdOK-mnUb{2jDD^=- zpL!dc?r!0@J3t;=i*eBFD|vV>UG!<=tuNiQhFOVisgo;3)Zt>8Aun*_ltnNJBbVD| zHoC(fyAQI3!ZXS9$<@1o)5eH*ji0>pUZQV2^YQ7z?jz3rV~1X>5&RVY(skPcHxaSI zXnY40mG?y1V?y!Sp&1Z!LEikYiozTKbyIxt{!BMk-$u=h=qK$}ii3A1Hyxps-aMwJ zu74)Ch`odjdr&7x_AIMpZF!&#E2XuZ1m?Cip#Rvh@pR#lfEl*MV<|9?c?Zy8F@<Dz z8?2i4GT`T};q4>Dq4e!~pLPLWm(dr5YtEB^)ho@{xED9@)=Gxu#-yDRuAtNNW`|me zV?g6II$eE$0W}P8?ZyaDC=EnD{&H(kc<GDKL6@qpu1<CsHy&-m-Y6X<-EnQ^n#*zC zb(iwi^z#9SRUu=%sRB-ENxGV<6bxAyK^crB3{wLAyCX2D?9c_fJIT^oFN+CQf{2;H zgHVrvpLFkBEOaYC(@>vD7pJ>c_WnUm;f)(f>s`rStwA)^^ZIq1UNAl94R|x$uAYY? z$fe1Yt7Ni>Oft`AN3I^!1y=Ye+^FZ$M&=y_?{+BHBlYt<@yDA=D9tlJb#OBzhK<)} z1~sPhKhDQ$$0QdM%V|QfL!r(`leZ0KkMUH5+lvED5-e9-ofYQ9rPIf>&?qd!AiFRq zP9B>PV}iPp#b4K#m+)X67WE&Nn6{I4)*o$%U>#OG_L#4Cv9Zz~O&ZPTl=Wyn?YvwJ zQ|U_9dxoX>M&abO#_3{NF}0hdQ#Toi?>#XCzoc2fO7lB9??p3%)|&G~s*%!kwgh-E z`Z}L-v&sX2nxec?rxD?pcGCA!Eep6>Mp+7l+PzEfjg?5{SA`67s2Leuk8hMJ#6YI& zXMVGkx3u_+<WBOSL|gD2ze9vh&$^o!wD$2myu2iW5E6I1yu29U$XZWzaj`?0ql3r^ z1$oRPB@Tp<DQnk>;(avy;#EkYOSS(IGuhzyn6a#=`o)!j8N{ytC461TET6gPMohSS z=dG;H2}m$PKgcML;v50^6}bF$l6GS$^J=qxO7+LEgLb5S#yE`kSZv5~vjQp0);#2v zTE_P?fk4mp$cNXb)4rm4z5hV}FWQuu!%=d7DA9k3=(Z4vM1m`Eg^DPPVq%OM9p8zn z7nFo7{w;qJ$z$0|FuLNN`T0oFh}*d-!jG@zgkI0#UhF!?6f?X<8V5g6aVs%BNEUGH z)Uc%uPWhuv8ittO8!G^VS!V+Eho&10GTyvr#ksAE*lv+IE*HNA-^a!lOjZzmf4$b| zU%|$yR}Q!YW}U17vFYXCc=HU=rDF;}#=P7sE2-P{Qqrv!2GZ$f$0Ph8fp-F0TE6$_ zj3lrMEaK5Avc69rSf!B>j5u=NUA@t4Ng8jUgC_AUT(4dpL8j&ON`j{a<6Fqgd=Tu{ znyN0(?_%`7h;oAu9+PP8CIL#8oOBq=D++0n3IrU9X>5S=j>5M6-%{XEy}i{(K6p`< z>e@^;1lp$MsR}gC;%AoXwWmpq=A`;d^|F6gqL{94@=TSiWH4M_b*laHe&yZrJG#hR zFb^aucVj@m(v-{WF7>qIV4;399I5+Bl<d?YI>&viqXQxxP8zOCY@Q`nc8)>*(;<+d z{-Kc>W2@-q=G0+!D|+eaZOFjTlrDf@omfO~jEGhYu_?|#O@)eYyB)vKASb8mP>yYd z>*}_oXH$I^@?w8TIiMjcB9U*OoLX)zw|u{0iQGfqO_1Xym}8Bj&rUoNZ{=wkMLIMj zN{*4TKemw}-#++-h|`e!%tV9WJ@=Cc_V`AwV9|;g)9Od31Ixb~pD_T2k_2BD{=JC8 zlDEtM<GW%b&w|gsrZm(GX{4p4dHIe`oNOxY(4>sY{aaWw4p5YCzCI@<nFeuoG3H09 z?C0o(R%M?kR9{0{1qKRkX-&jrfj*p!PxVb~xh-8)HaClM;(^qaL*<qz+1vK)ad#g@ zi19-|gh@K<XJfM7!B{9A?LI4rVWQ1H;YSNvPIpn*b9mr3rd3%bkJEv1L*>knJlAAH zdPb13@GuNyfubdgn9Gj}LEfX!SHOfF20xACDOT&+1d{uB_!MzQf@+iu@}{h|ZIH__ z!>-0rH}IQtJBwsS;xKm7Ztt3Wl9>1_u8_|*)o~wE^RIe0nZqCyU91{9qf7qvaDBEP zAmE=;R8>9+_Xj4mS;PC0IHUGEOvzN75(i4QTJnY0q{Y_8kc(@7vA782nxH@^xV-Vf zIYR;K_LZ<+9!@2~gn9h%!5&0}5`BDjv|HGto|!6ixlvz=ridtck@x0kJUjS5nEycn z>k0M5X#K~>m3JBG=>YC+T;O1(bg<C-MYap={!lRMH8TYT1$9vP(c-oFT|3mk`*etE z46;gP5m^>+P5Gu;&4m0GFxx(@giyO1zt9t^)z!pOZCE6qqpZZ1fF>@Pm&0^-m$V8r z5Y$875sRbp0?Gtxq=IZx{7fX}c7N+_qtfwkSBMzd9v-KVkQ$vbCPZAHdNgBF4!U{D z&L5hZW48uhz3k2FXc-vX`ejbdm)g8pIbAzghWN^jgsnCZD>rUKJj>8&j37bq@R3Qe zAb&oFY@RA2gF0^*>5aX}vlNudzD{4Il?oU?Ce46EG4lwu)UFD;jDNlJ!c^9(((`e{ z=1qeP0zruT6^cBEZeurDHIZjbpNE#1(VJzq639UDRL}Pe*3^>12am{=&bJEBZ2FCl z?4xO?H4Yt3&%I`XRuBOBBYUS|79n1F$nPsI8=};t<?aO>&dno($u3Ge|8JXOm2GDD zoKF=M6zHS^<WPmxe=nNJ3~A-xbH|8`rk2E=wx{zycc%=uv{>4f4BE{M(;=-kM^b+R zuP<wKrSA~n>m5v*21BjrqaSFq#@04WL#gxYufT2!1T>utweKeojc`<`ZUjb6ahA1= zDF=toDmS+%-x%Q$(cRdbn3v@uZ;<v(9kGyC<@CYN(A#<HFut}2AfI78BKm=W31=pW z%VtZ-$nu4%i?xyVF;{O@24vC<+-bCoQSAqZVtd91lyD`C`F6X>ev#$Fg!G`uB1M0c z`9{&!_bc<*+*0C%v_4wyC^JI!_iMuG4!$O;R#H;WOJK9N7oXF-`8t1Dx_D`O7^nt} zl`_)wm3uc5&VWdpYxqr*n~^%%uDcQiSD>#%l4R%~dxCot3AqBA^)InAlDH!xB6RPV z6DxiGLaP$Sx<)ugY-B!r<C=Ei8aKnQF`LBvbj#y_-B{4A-<zTwu`FJBumrpcemf=^ z!e(G}x>bWAixuQC!S1MkVqz_wSIRQK5Hgd`M+T}iJLxGp5g5*ZX4AvGkQ~0gjTnkK zOt+@*X;0a;TgRLy<&;&!!R%`#4&^MVptL6>R9ze)VPW!Twjj5q_CosC_wJy891M4I zdH97+BG6=w{_Xs(pZ{``zcDINc%}tpj1i={x)u=Z{86ff6YgFd&K)3|YcbG|ktCJr z<ZMll`pobF`~BFcJatMsQVK*JsnIK^xA9dgdn#XrBwc3F4#?ZDGXpUwK@Blq-?5`8 z(ke31%f}j%zR7l5`1CCJ^i24ZjTsl!Gu5-mgLR<k6hs>`RpND#HM7X&Q(OuGuMW~K zvF}(vD!nNcg~^`o()>EYWrH8qE{`8v4kF|3SXq<GzoO{KR@gt){lmO9hIAdU1`{Ps z>u2b&d`H_t)G&H7FCJNO0cQt?=5?@}Vl<?C<jJlxTKQym1P)wTT^;0zX>LYtdUN8& zrnn0<Z5O=3NVh(hmYyxnO&BoWoGgqeg0i|}u?6_~c}?592}K+Y%sG;z2x2v0C6y}W zTK>o`e|)oG6;z_faH6-qw97()u8vo9i0zM#aH2)vF&j|T)^@u24E*>D{-<9f(*8&L z$SB{Vup-u|n3y@80-{__T}p<o@Dt>%__cdM-M1s+1h+NswhT_IFAR@~qy-x?-7%mn zje3`Nc{(?8zLp-H-@Ap|#qrq%{p?~0REOazm+wU~uE<ZgHE~}zz)pizydkq^7>}BB zH{fgj$T0C!%smW_tgYvJ^p^vuSI~v;$HRe1X?c0CYYRYvnEPhpLJk;}?rIF37gvtT z7SRk3z0jgMshTIE6CKxqR~xB7wL_PFm)FJ&T;Fp;jkpg?J(lg8)mHRzs{6H3=RD?x z8MUo#R}J_cy1i_%Kt1(%{@0dw`y|wOi+fL>`tUIT1GPcUC*8I%m%l_k1CM$bxN2%@ z_$8Q4$F_9>Vf}sV$uV9&6}>9-ju#nZ(!}xfsl|-f8@D2QErH)BI|@QKzJ@tlWFLO@ zGE$*i!=%@y*S;<%hm~w{klDde(r!waq?TnvO@n}ls>6Xu>vpEEsuePgPj{X~<WDXl z<Dlz}n{wKi7(TUKTI{q%Bqop`c!B|csTo_n#_7Z{A?9H58jgSfFzC@UaymK^Dmrr1 z?7)QN5BLatq|vIUfvmyda@|C8amm!z&3M6XG=)|D@u5nY2HPg=D!jb>;F~PUz~=Pi zM5@W|Ecu`{HtSUo=Ax4m=iYRA8i-2Bd4GGQ4#FT2*qLQ;BlmhP@B;Y-`}z`z?qQD8 z<WpZ;L!kmOw*!PRr`UBBv#a%W1@XaB%`N<{!4-bCt`xYRz`E-*K+VO)MFFjM@9!MD zt1ui1%87lE+azu@b`cj4a_?ZF;c%*U;RhOud@kaGJfn;X;K-%3T8Z*B6#e>OaQV}m z;b`8kzW6CmJq_1}`ogwC5{KDh?)5zC4V1t9oQ;=t$$uPbPr!X-B|LeUY(Z^_Ea?8a zReJBz`GY>~w1H)5j#d4)6pxkl?+jV&jE^X8K4@l64-AiT<xvlqi}!kbm$#aRlM+=_ znmoF8h3#|ZF*~<$w}Jor{}9eCqk$GAyOwg%?O)FFIQmgd?U+~P^nB~x@%ql$q&mAc z9nFWr(~A2#Pr;QBF6eY7C1;l*Gy#|U-G8PL>&LpTH@SV00R}H@zeccv1bMeQi)$;P z7t-hyq^IrV^)@&|BFIcq!fsmd4eZ}vFt?y>0ngAyx&WJjEgpz?ozukl&!2qK(jsCk zh~VDOdm^8Z+SB+1O@5mi5eE)VAtY7>TF2_xW@efCBMehbYJz{;NhvFF#U@Bv|JB8b zNsB&72j47{<-*I#_G`oFlzEXG#{bh_b~siOf<~<A4sg^q-Jr&4d*9i)OgmG``fn>( zMjALR-K`kaI0jw#<hInxK#E{{5#K#oyrwJiqcR3u2+JDTj_bhG8N>hnph|z=;@{iL zM@9Du|Gl#O-(>MZOCEky&w#bBVXg+us&XN`e0RsHb+;PSc6QKfl(AX(*C%;k0^auv zXzdx-uwc|-pdKplZYF?|^-FWYm+E=@KG;H+<ye$}Lei^zRUUZF<>cqD+gF{FwxIg~ zK91y6XI}0{F=eWx2^02NIn3YfY(dnsQ1i<MR^G)XkF{Xuz!wK{aA4KPDZy^HK5Fjg z>=)w~t&(E(URAXl-4Eb^rofK&OY_Xljg?$U)aS1$U7U?D<ZUMwgZf`=PouAd2V25M zN19c!b&#uXCrV`INCKl5Z}GNY*|#^JsU-TGcC<V6?M&Z|@L*vM-qSJMy*XcgvsJOk z<B1h`yl!N=)b_tvz(#LM)AJzlu`V29{hq|S^8>thjc#-HZ?oPdC)*N~l!mTPb`(}N zIBS{HVbRz+yw{u=85sYS@h3~f(rGWj|H$6ak^j+1r1Y+<(rKG3^}a<vwr8<L(N4V9 zIM?9NB*{^|ahXKC_C!$IVoSZ-Psx0V=aPuS@%fR3&$8Td@Kc&ImcNYTM~)MMd8{b; zTwHG`FT9DjuTT7g)Z4trf|5}8gLfW|;gE9g4w)<|(|P-uj5d!mbPJs#E@EE>sQ?1& zxkx?9O03!LD&FOTaltKJY)*dF#g$cYTy&BI5kcB$!QWJsO3Wa}7d!I`0Or_?_LjR& zc_Y3!jkoIHayVcw`05iCW4iFe>_WTB2X4!rr#HkDMFRT7FX$(@i1RF$OlesOj!b7} zRG>xTIV3l+rh#dRmOK>NHBP(RXyNH(K`B-dw=sQS?6XmBVV)J)H_k>1oBC0kV}%79 zCN04tqTHA{Xi=-Z&cpSleE5L6)c5SLgPi6PdU5*(Xx25xeCG;YUw6-vloSAT4Clqg zW^Hb60$RjlsTQHZA7lB_d#gIv>xKa8of1mD1K@z>R#n>Np2ip&(A`xJP<uka{`XWY z<%HE*0Q!<ap(Ja5VLq_gz5+4+A~(xf+<j9ikko|=E_SG`aOqjsnBRDHJ2SQTU~#`Z zZ3^bUb&v42$?K`4&(WyI>euD)nTa1gTZj<$yz2w$U_L3n`q!o135pj}DUu?*SuFVe z&meKB2dmc7Rr+{o(cM|C0ZtQ#PtOX8+{Rvg%j6*2pV=_BH%GxaC(5|Eqv5RtmCyBy z+he|P9&E7}Q%&>`I?v?hv_s!6fLe*)Wxy~p@K5>qIHumavn?M6ST0gZVy+RWLboq( ztgUTDMd!zDbt-A}Rgecb2^Dan*5I3v)YKx=D22}f-VXs}o~yquj(ZL46am*2J3&)N zxt_ex4_iO`)oyg*lM1di#O9Db!u0`bKujia3wbg)3*O>@s(uadfN=$r+aFT$Ia*xK z7Dm*ac%Gf^B4<+hOJJX&gpE(qEKo6d&sW9}4VdF?x|H%aY9`H8tW+J`#1_QxoS)^e z5GV{2v*Wt-b{`VC_I6W=$}Ozx2yFHP1>pVJ8Xu{*=Da>7#W9mageFs@c^ft!iu)h- zAG~u(Cn7u_N?u<ij8c93ZtCI35fh?<zHZ{Zr93MYy@0yea)A0Ut#ohC0LpTdPOMc( zL}YKey}8>Ow0kp-6oXug6qQO42S1}y+1h)*!e@d-i`cHN1h<M^gQERgg15#pA^(TH zw|<B!YWsds1f;|u1OzO)TZa;q?(S~s?ob4zrMnrB?q)zq9lE<?7`i*o!uz@3=RJSJ zIsD3=J!|$}Ywv4a*XR4aG=S`FZy>+h=eG7Tq!uw?fP@0cLSfAdOQn7rg>n9)v93Oy zTdR(-Qz99~t}5z2v}#L@2!n``DjW-X$>7pH-Nw;+tIEaWYvj!J#AL<{FES_6i$kT< zIQl~{LBCcr^pP*^J;_BZB4P!{i)c0#`~lAUSOiQDX0;D?!^aWf2n-hE51B|8pOU=C zse~4$hE6B&;&~Oz%zmN4sjVPCGh^#nk9Rg3wn;;+Jv~!XODEU1JNb!fUDkvIq_@+- zR#g(wq9?sQQ7QGF(OJ0>I;qAsriVjCgGaCdp@0)borR23ti6*Nr!xjGh^ncmDTYVe zrutG>UV_7Bvm!pK;svW@R5oj)R>_&$q)9L@?V?uK@UgX7yU+BakWX1Py`*v?N%#L$ zPe;hLOYU#~j*cf67I5>La}D;ccMF?ol+bDmd{Rzpp6Ec0SII(@oSYnAN0dK{9imll z=hH6(Ux+)Aohl<%pPZ2_<fdr4e5sHqT)HgNQj{9lCuV$bk8dg;aqTkv8;D&+@OHcf z7=lY4tsoG(%}<6Z9SXtMr`6+M^<VI%!_HsSs%}n8(*Aid*g}78TW1rT>sVDABq^rA zc!i>ULc&=_>R)>Ftfso0WGFAXuXJ~!-r{JO{C%0GhWpw^F~;q*x9j2-kcK*v(Sb#t zUSfre65N{&RCP($Lnl^?X^vPxZ0qY=6Oxd(q5V2a&Z6MgPG4HX!i>nW>La@&+kiht z!3N4Z#y25C{1EB9oVZRT&nkheE>;>xmTc`KGy>gG)X$DOGH%K0&jRE)TH)VXN#TQ> zI7iUM=GiI!aLF7`?f9DBZ><LO%bTl$h>KTv+3QB}LY%>nhAImYJLXfmR6++@!AF$U zgs^x1eaKk$F}vH72q*3x{+ym<C8}xf1rEl=RrTz8TA&2I?69#YK@a?CniqpF><hog zLppUo#xUoXZwM}U$H*quN;gK}ZH@NjyN-w^hmRrF6D?w_YUDcJf_Ezm_cIr>$!`Gw z{7p<tJVqv0);lNQ$KCFocFv0SwQ#w-pbrFhD7Yibg;1)zf&)P)nZCM*EW{T_zZlK@ zAWws@@SsEbh3D<7+x(jAE~dIhNHZi)4`|xXw&IoX%jHzk*RmGEqJ}mmk-?aJ7o+<} zht8&ywEN2lNhnYRS!(Il+wN#C^LQcGqVvQ{d&y)MU+_U4p;A=yE?c~{-Mufkzc<gv zlH!Gujzu#$4=~~Xv1qqmnNwaOSzB3!u~<FOw#d*oJb7I`AUaqj)xev>%D=I%2*(lN zwi6>A&C5t(+2#V^IdU!&?bND(kd(#RjCtpcXjdl%AL%F7n!IH8u>8%<^Yc=R9lq~b zO@)iFk?C$_1ovo(u;1c!qy4a=PHNQj#eKaCG&|-&W&^dBl0q&DG3oxCZoZ@J>=vTj z2+J`RUrq4_nja}ieMiH!G;I`_-r6?7L*@cGI0D+|8v_mdtE8tiP^n8JGgl664#5!P zyT@IU<evgKw@;DB@p1>nb=lI{`DW@gS1NBsdglX2cj<^J#L;I`m-KhuOyB!_yI)+} zCvg@OlIN;@?F)LQIK+{gIngBO%T2T&z#r=DVwb}m7VIT~_>Dg*XHYDtt$W-av!mE0 z@5HM`Qza%P16`b`Yd`9-^v+F+g3hH`JCf^Gu@zLhJ+ivzK2M$+oSSDWT=FB3FL?9@ zze+7p%Xx8%EVp3Rebq$lQYDO0N&L7>bg<cp_YC%UunQ+57`hi4+jfl+X_dsO)Fp}k zt=8<tpIZi~5UYs89FgE}9pBRN>%hUH8HuYi{BSNCE!Fw0BUTu%R^591aIU)Fw;YZs zS+fNG8;9tCPwnlDI^|(s+ny9;g!d=F3fnnz{)B~iC)#;LCh3wzRE748YCf84y8LnL zG_QJHr@>VWk`=5h+dWTMWk(D(IE(u|usx_Tc$h9$=OXjnT}_}flA}onCx;;V!_p$a zn)P_oa_-w05`E!C@rgH=IlS<ze3{goZ9bv#*&DeXZZ@j$lBiDtZghYDl8qFiZg1e# zeH3)XAtSuAZ9y4PQ=Y}Hg4QR-2hzt^jr@p85*+leuixSK29`Xf^N)%VyR%|G3WLv8 z;s~{tKZe6n*?PpJyE7iE8cM+HxB2bE`~2Eo#~i=f96ttcHhibI(465Q*KBwGCS$_x zhlz<n;60jHK}~I3saB%=mO{YK$W(KLCb9+v4ej>QIkEFdT;mD8B9w{N;JwSL$}UrJ zDO#Xhc!Nja*Jl?aRZ>Kz7&q<Uor`btHZRwXK(X65E<K)^&x11I=4_eMK3N3b6DOxJ z+S#CX63Z{|YQ#Wa`&6L08cOLyG4LZQO`KS-<eXL4z@CH>FOd!cJ^PWS5cR_u^M*rY zN1L)FGc~OGf)cDRPOKHPSfJ*pGFaw!KRsb2b+H<s(Vh%*&h2clyXR~#A&)-VS@2W7 zT#TV&p;|8E;Sa*(_|lm|$?Et5b#=9$fxSJS@KX?y#%m^!aDgk`Xt8RSL7Q~)VfG5E z;^MPv5uiIw5WKj%B^*VYl7*sAm??w#v{Gr#@;*S`@&-TB#m74=^}GwBKZG6PxPKWF zb@EOW{VSFFI|vFr2Rn=F=FgWc$tV43?>_0yP!fi49;6I;5-+Dx57RQFUKYq^Ba4#A z$;h^<h=e0+t_IZM=e(hzsr)7=#RUHInTvz?D2i#)rj07NeKK(OZXjzeT}HwwWbZxS zRUe#ccsD(a&|vYxr@vX$N`2wT)MA<!*HSzoQs{C5w-T_yqbU9AQeImr-v4JwHZdwL zlcY3sUSj4G1Q-b@yc=wi*9#mAQ?H){ehep4!y4iqbF02=>{t7-IGtO4kDP_R=#6CT zkSuTF@4y=I(zCsMg|hiX`?yPX`y-Y@?_RGTy%S7lbd<g+G#-f(S<@IdoUWhqWJd72 z8l$7NRarY?euUL%t?FZV@Tf>YXC>*eyuKA}8Q|8ivr!gF;rKk5hgdt7zdVvZTCQuq zC|GO8MfLLA<+HU{>KiR1C;7*Ej_4_?qBVSyCOsQo?#O7opxWEF*L>%(bsZ?*&9ybE znjEj-5VMuF_4+jCDgXXoT9V<JhFdv4sl|KHW5={c508c+x4rVp@q>eddTSe!vExjf zy^qHIY9h*hM<=#URkb1EVK!R*5x04zuPmZ3Zeq}-B&5d^=0??B%mnYf*H)ZxTDSak z6ur}QGQ^)&a+G_u7RkNCyJzdL^p(PpE3$bs#PbEw`X;p89YS4GRhIEwYCenR*WrmH zb~8~4d30RlIn73pY-&{US=^t!E@t^kk$bYw6lpLD_gRaTF%=r_+b>-mzw&~>I4qPt zyRXH;lCD{ZD_vqg<4QIX*{$El_0o0xy<{$gwXM|b6jK|4)j*(e6kbaqr=#jM*zOu| zs!$@pk}n0P)3`>lX@EF$GYIho2x!q!-VCgt%IG?}jzm)x1T2MZo_F$!H$6K5a`@o{ zdLTUj;H{N%cJzC!ca7CH<;%a!{e^Zmwi04ez$q#elc{E;wdx4{3ZA?6>=ru9;qcnP zS+=$8{;BO6D%^I1o?JI7_?90Is3$7*rTg~j0;QgG-i?XLQkU#gu<;WxH`4VwH-g|j zYCiq(^}$XYJMQNB4xjTE^bOK++#<2JDI8h-0-L9A^Ng!31_x$4XlXgO*HQz)Rz`7t zF)qSB^t!Qjx(RXSC(0^Ka;BKZ@){C;zW`~7z9E_BVl(gA-K-c#xVEjqr4;dEq)~BT zyNrPJ1Z>=5?X`}=G5?w~_r5S*p&Byn`9VH^)qGP(x!F)-v$l)`n+FO{ffR!purWej zG2k1fH`%rg&nDKqZxLrSoheHTQMH^d+k8IGID7*j8_VLr)6JexhrN|m54_j9Zq`L= zZ--p}-rdUD6UNe+hv!b`aZKAu@&lAlgLec%J@E-nPri!Bd;umZa=sCW3>`AsfOJU1 zZA&s9(6$ZamdQ6ndZ^})l__})=JHX~3`+_SThL<%y*+KM=81vj9GlP4WT>~Hz_k^9 zKkMtCaJ)KSI_B~7a9N>qiVp^%KyOE+O2S-xMK6b4hD|+$9Fa}&W?LUEbK}3c0O3s+ z^1D~I8bff9bjI`8PHZ%2bFk_XURd+#Hbx8)8wna6s-6!8^He`_DZXKNi{f@JhVt+E zN|^e;%<Jcvjo97<j*5uN?AgDE-0NENzx&4i{vMl@XZKh`YzwmPE&0^mRyy||4vU(# z|B9hc=5d=yGeiiSc_+p~SHItA(^#~FDRI+<yIpVIzkd{JX)1cj>gQoL4;2YxgW;nT z+6-*$Zi^#+D<l>*50Wj)LH5JkmR(-(0@!o4#}X5okObUBQk6G33_fs!d<`&@vGM24 z>ZxprFa>+ddWkYfwZ-K3`wNeXllPt}s>H3?z*x+eOAIY)$d;_xFE>)kW0*`jtzLVr zle+mVjuc<Yq&C`zJgwni?ohgbFj@@0vo)LV<1qGoYOLrqgHcyfI&N{;saWT_t@5*8 zZA-ChI~6Uu>e6ToVW)N9|HIVjJ-Yqn`0s@0@RV|oh?Km&wC0ERo^jwDEkdL;Dvy6C z(WUYj)121epUypU`}8W&hRlV@%?yNuQ=A;$%m;<b-pC6WOtfFod8&6Z^JAgNwTI(^ zP7=4%J8kVL?*$weiiJTx_oJPmq}ZS@u#vH;&pSE#j&26XsaZ#~PhJ!YSo^I<k(<sh z&TXdku@1~{GrJ#8pQLAHKcMGp6Q~j?@NV*g&C~}Bz?t?p+~+aFEFXVI|8=*>Ig?-V z@WdzLK(uphH#c1nl~Z38tYSW0FT@g)Tg&vsT<ta*r%rJYbC&TGa($kkQ|`5z^f<Uk zWwErq`#1~vROP4=@O&{xCSHbXo6S}==L@&&i{~rkTYk{DekRopp31Kye{8l~*lE4i zz9ue0n6%z~5C!rIr6k{(oc9^=VU;-QZP1%bmS@X5OJ~;@*v}K52Yb7a@h7K6G7^UH zYNd)i+8Qf#_z&x;$1z;+fAWXe#CrcuhQfmR(Dz@m3gy?^mni@8XDBGqz9|2~AyCe} z|G(MC|Np@MUY@;BR6T#8SJ8isB@Te^qY_<oHvw4T8Sc-p&<x7|^hhy0#rz}zbYQ>e zo=Hdn9xAAyxt&AmM`~(!T4Yhti=ej=T;G7^&+3VT!zzW<6L4NRm^^+Gu&-;m@^tUX z%L8&9F0QyD`;7JnAXiclEZOsvFBv7FMAOtr%+iu!YpVzub@v|s`Im+`IW0|ExBv!o z>ga^hu--$-;L2}$IxN=z65xyW4)zDy+S|wt&bxyd*AJY9gpy)o-)m2s5Zt4r0uiqS zfsO%5X#wiO$Owa^Bs(>==G|yWp*cn%#m4h)qU=C02sr#zYYN9EKtb7;C3e->Ij*Kg z<?aWc!2kR(GG2|z`y_gIP;SN*rHQ$+cCkg!+xQZrYz7P<&jF5q@YdE)EPfTLyV3pn z6jRyG;75n$3sq2;k>TI4yNBW%l<`{j_21~fD@8GK2KP~X#c3Ua)VY*Fl=gXgbTkc2 z&;%TA?!coXOBIQKCmD$;Zf8gaiR#J<dvHAl{3yX8_;hDQ#f-o|Gc+CT?zbgmu*D=8 zt03={31$8evj@sopoj4dDzB}L6Fc}x!(n0&lK{kW6%xt@aiUO+Uu=n5Z=YiM-v<{! z&*10T8~b|s?WBL>`Aer9q+Y^p5GzF&o0y!;3f4}vku_7CL>OJ4{M~?Q*_+!LA`1SX zBqwGvg1kWjjj|mRyNSfVFMx+Kvvy~HX8T3cclZ0{6~_N8z4$jkwq?nlgP%uV2|M0{ z&#*0*nr4F=j-sf-NxUGzY_@9eFQz2?&ouL=_0PvSSpx~Q|JT=%I78pdcG~AK=yn=m zQK?=d-{RBUe!6c$We{gz!gMEpp!#<md}~Oap_n2nL$!=)t#L|n;@RTp4HMnq_OpFo z)w4~d!PDs8-Bm?7%D<;v{PvuIKLon=Tr<uho7mi0<DoWosk=?nb>ts-rTih&%<zde z`oJ(k*Hh91o{*sC9(2QHHF-xQOXTOqCn%dwhr_fINuLla*I<4oM9rfBqkuMG{8B<u z6xVuH>zC!9BL-K?ig>}}M~{Z=|6Q!p*mte#ap?Ewr`g@i<5P-$%FfMwdYSIMJ8AQl zApQn-3Sj3*P}|3CAn$=)z&Cdl#it6qY*bY2?Dk{r%ImJ^Rc7aY_7vQb+((HKtuDDQ zEzC^ti(ukW5}`1*V}$~j105<mdu7@zkEyY#ZoT_*XYX|5DOm%BXJ2b;>z-E2)fw6~ zxTn4nie3^u0MT6o>g%-t@DT$8129m6#|z^%(vm8vp``3uR6&M!)1$x!J)!YRYZ2ya zN9pdJ*f0)QR^BF`?)vD<=G(Pr?j!^R4wrpiBxj}_J~N~1RaK7e?gDQ^JsNg@J;_x@ z(`ZYqB${^KFx@4YFkMLSc%_d%cRfydJ7O~wNoZ`oJDp`!dX7EUr27tb!AN*pUSwD+ z=s4)L{)ZN@-Wytd4u`r<`=8sP6+EZUg-q#aY4c|!V<vO=E7zD(4zDbZbjX`~(|q=e zudX}|b<j|JO`#gG37Rfj&@RBd`p?2XJu&fCVIW8ac)XcW-j3g`)&(=J9P%$gOyRh7 zaA<HYTX$<;VA>d_*`Uq9yCjiJIq#(Q_P5p*^WM5#=2M<7>A8M^vL_{)kHutD>pz(8 zOBg>3qhK>&J00n6dC#Za=uMBiJL{96`I5l%ilh&sDh2yBP`3BwBeh)SBor|~)_Agc zHG&Q|<M*<`Cpoc&hZ2!+Y#VKA#Rr?v^%}FSI;-Y2nq!A1vV;Wh$}gMDHF)hJhrhWo zHwmJ#T9q6UmDpmdrsK$=rybOW?;K;`CO;=F<oPr1!X`Jx`2gjo9k@SrY;gNWj9i?7 z^^;|6pxVBOo|2k+w%oBaWF?E02~dEvNFW(7PyF$N!+OX8or>Rc<y&K;g`W0p&I&%= z)y^FDd%KOT;tWNg89}okcqZwu8{fC>FSMBXhFU>km?>pUUIrk%r5_u)gfbW8KaIo| zjw2(`$BJ(x`F_V)d6;3|c=L03cN93St(gFSwBpFp`@q(*P=%6;h=`JkDmfqiiH;2j z>3_pUYMesOzI~#*w6wB<IGITr^774OXB+xi&uollhbU2HXKVsP%_Hi^4|P1fYupnl zJg+zjH1FMbgGimv!|}b>+Wj!g$*lb3!jQbY#!f89-LatHJO?7C?$ra}_SexxJ@h9e zzC<P+a?{#T$T5@agiTE?dcy!w4ILVvDO{%R{|krbe(6^IYQlSZko|+++PZ|qM#F&E zY9Z_=@AiPxOBh+IQWfwK)EJPaOQN;CJ}Hbi)BNx{l0_NEhjTpumP#g^a}-lUP^_?? ziv3z%@OIXbZSN*fcc?S7G9<f87LSZ`b;JM76oUFf1|8lM00Jms&fdGH(p9ioM-Cn% zZ{ty-WWxen8yAWVJ34AC6<A|3kDqiAV9GoQ3Wjzg9?dm-yI;iYqf_1N&I{0-H(l-O zdSCBLGNDPM4W%}|+Q77TaEPFo^FC>xJDqcj&4-(pl#M+0zJ>pWk;Y$L^j5U^r15=p z`{H@~JNpV#ODpAdTghy_-C8u>d}bbSoggE-p7^5*-k-tBwNDSXxplI-fD7vb4ogu= zDr0HdY%xmwu&d2eiU2&~52yfih#|~v5k$SH`aBsL9IHJ&c6GL$mpsnA{8@tHy?a!P z6opM`8BhGz(p0Srge>V8!>WjazNn;H<pEw8!NQUimbzaFhT`*{7|QrVe@Xa2yho0n zrXgV}eXh6D`ofJoUWtuHZD>Hg9@JL*QZZglgw!>=wE5iMKyvDl>}#9B*iGf`_<;ui zv2wAu4|eYaPFb@pPH9-yxf0mp`v*99^#^UW=|hTeJ<%?Fg7&I>S+ZHD>x2Dlj977- z;7%mG`UGo}hnVINJT*t~M_Ex+Xw*c=3oVK3rR3q*YbGWpN=42}+K(xLn`@y6r=c)y zJh1WMQY0AdP2$S^LbTS;#<JhXhjE|O<>&NIuibmu^BPC2+-$)_o<r3L9lnMk-}F*E z?f|Bx&kUdFV!aQqZAzR-xUEKE^BH!9H`8KohKJdsqM}BJkJiT$`v}bR&Iazy1U7gd z*8E#2$^`+GYlY1HEpEpvtwe~6b6V^-4J4P~@!y|zS}^BtYNYUJxR3IOeW((P-K?=N zK}!|HqVp{t9yiGXIqu=f$>)(WentOAB4(ZH^71r3!=bR`T15qg`l(jF1{ZzkKG0a0 zB?1lMa@x?S?Y6lDLpE-mgDf#M@Y!n{MNQvNIl#J4Ib<`}kez07(9Q~EV+ugxCPCUh z->(Xj00_dU;t*BVi+8w3$kExpzBu%+CG#V$qJ}c)5{>`nw^Pym)HlWz@TP>`NZJ@1 zBjqGbGJ7ju^9A_mrlU9yBcs=75-zaB@&E90fokEt9$j~s)=O?!gF#ec;#{epg!RrC z8qD3<YZdC|=;bn1M1-Vm!7Kqwjr%1&p(X`?1I}%?!D{tk;xM&LD(}upzQg=YnOgM= z^Po-jn!Aa7pw$1g(yg+y<@$=?dbZi&w%f*guEOdRP>09TR~OS1*3;$VzklD1#v_h* zMz`~HZ?cqfL@=HK^SUAqnl93(7Yj>J3&+$3pj5=)zd72}ZDF`c)+};HpJ2hmGxj%i z(k~t;u(6gR*3JTTzzFj9F#?_4mFtSb#1BC(uC9(oQ}6F8+C0qt1F9->LzWCWD0ZTJ zkd`MyU6>g<DkwW-^7z@HV{X+6xhPZ7k+O7D;gMBoKvC=MY7Cd7$+1XJOoWRF?)Y1M zX{8XZ`8)}<uF)b)dB%si@k|)n4NpJCYj1CFhri${wvnLwQ~%{w<wkHk(`EbpANX4a zh7|H-4(qdBHe-30^*y_7b8s91$w`^MpL@5;Racn#Y(e6GW*S|2JMWXeuLkNtQ)BvG z_i?55JcHglFKtD>LB}H>7#|*<1@!Ujzk?Ax5C5AB$czLJb^ILpL38u`Ca3$V?)G)} ztq;Bprh8X?7piudyOi=s+`#T_hr^>t|G@jkY9GxuI^BR^KuJ$t!eJ&sf40F<56%Vf z$#Sp8F%n$g_fzyHO$xDtQ#K<_=7#WSYAVW>>&}o3t~SSfm|ZqHh;@o$Xar$SmuW!o zSTtS6$jKSj2_8OLh9qs&s8EfM=qPf-N?j;*N<@`D@48>RGRie=O^CuKf98txYEC6n z0*M$PrN5xJlJhS?&Y!c@QSjB+Ob0Vn3?=MGBSvudc$}9)BeAviuC|Qn$Em2OjyD8? zndE_Eiq3otqb3J*DZLLz$8Gs~tXQMy#^xD)^jL8$bOBIQrLgmR+G094yO3@3^&zKe znQK5Z)sZuZhCK(iI2J2~Ag+tWiBG`zI5|VsBSzm@_8a%O`Lkz`8|Qki^MiLzLu$d^ z<{6m<_V%BQl*V;FT3K1f-Cj!q!jwk7$qK>G%^g2bX6wX~kGC33`^WR@>@Nm|56o^j zT!*jGYj19oWZ1UXw;l_Y%2Vy)?3`r~V^KZwQJGII`6QS%JGLNn%scyZ-pdNO;jyxo zGfp4pR(?;JnE_G6#N=cyFRmdJc(}cHyEtS_;$Gx0O-|lCD{te>W%Xo}o3wa{vS+E> z#r3OVXym6hU-)~qikoA#Gfm4|sj<FfSXCg*r*3Kfd;SKSysgL8ih8*ihx^J8a8DzM zp+(rADEEPsaY%So^oe}FIa1(OZ%f+;m@zglwm@Sznw>7^5*^~}tl1s2x~rzSOS5Vx zV5bA&+Lzw9+oUQ~4kpsqSJ9^%Xy{ir<5+N2pYzGfsbQ%X+#6uU4w~9IIRORkjDXLL z(ba!0F_Dpr60)spH|{Dgt<KV>{j?iTmsh|AJ)YFj&TeO}c$iz)`>d@N4Q(c8KO5qr zP@@HNeE&Uo#4_kxZeB%D=ha!a?(rs?9IfJ#rsvJyM*OwA(BY|&kW;%}jOUt)FO#gx z=1?ShgY(WWSWfA^vj4rl06`aw4_AnkumJAa{vM}oRN)lWXNXeu#4}fqV%@{j=sH)4 z(Ua}j>w!6G5<dCLC>eA*58QhwX^eu-&P;_;n3(3?-jputzfa$0JJvc(cn88N;)}^k zas2*L5KWHfO=OV+%OJ`g8hjR6P4v&(kkb~sL$25mErDpLI|10d1O-djIylhSRbVl* z`art9`Bfj!ldN5nc~p{m6gJLb${DEmfPKv1l0e*OsVeu%2^v^2?8NF|^H;rvmK57J zVbU`hoa(K#ceL7P>B)Z59~)&FlYK~ax6Ljndhzk!KKP@PC90N2l&A4%kyqx{;SHK$ zl1D|ci<%?dq0=4Xl`EdJ0om|WqNM`6nV+)-h6L*qp$bw`gS(-OTLv7J)p~&@tUjw= z_tXryHY>Fz$&R*awmM{bv~pZdAkKH2Y6WE-0y7{e&KI@ZFfqwFex_d95J2_e?%QLF zu?jwnZ{e^IUF*2@G?V67A~{?+D{J+1arLe@D>;`Z*Q<eVm8GkpN`K<ru9*bOtiQ+9 zPGlx!t~+j2SRI^$Dgzzk*T(wke0h-#pYdVN>7#N_;^gWu*ua-rNxl2`JQ^{wKpLAx zTSg`6URkZSpcU=Y#8&b``#<4#d;VYeBO@pYS@VCBCwi&3TK0=fwVjV%UGC9rZj^dn zw7juI5*S}2MwiugKY$CMm-IEgjCE5zT*={T9Cf0o#?t9WMf-|c@ML&?5tQgIDKY!m z2nFb@#_@qwTWw`HzvcD=lUF9pE(X)P75=G@ukX?UJme*z-nCM+TnEvHaO*d;D7js^ zJHSmE9{G%mhFav#+@e~i7uy$Cv?V7EJH$2l?zg{7+?W&Az77s^EYq{ExA!GCSGz92 z;fCBtL3t)ewu@@HAc6y@YSvV@)e=1Ncp1|>*$KI<xW&qzS9qe5tjW(CE_t;Rfr~wN z?RL8_S=4!9(OdE+ue1Wbf6iR!w+ll1i{bdMKH4?0%2kG@em82>iiSo)3RB&;m$Ax0 zWElO;*XO#*C(fhl%GM`O&3Dsd%iXJ&KinfIgu}~CH4W(SklnF?j;qX@e)B-2u%jy3 zmYr7iIsbETD5&3#^WUWMy@7PEvK8{ruN_b8>)xDD6q>*t?3IfptXS+oz($DF&998y z(!#&A`_6VTOVO$`k#Bk0=-yPZuR#1<H)Z(u^{J0b)5YMDur8B!{r<1EKWZdy2d!a4 z<Y+%%47us?dG9m?CJ$5Sw(wWkP^)NPS31k$Lbgv%Pu53jFbkh@jS>1?Ix>th$Uakm z%i#dvhyvnvo=QYLT+&)lSmDL-X5Xci&GMu`X=x*N5k=w6BYe4zPs+-nq`C&)M{RHi zRcrQa<qbi<L!9nX)3dQy4Wp4F#rxsS4lK{)U+Jp5$&`$A7!X{o+`}mhSI)qVsi;|l zcWK%&&_{T=eselBy$z=~Jd*s>Nq}HWyaGrOx5e|abtvta3L*DyfMRAVoszqV3aI?N zmUn6*m;ZQv={i7B_!Jl%zd?62j%DWL>mHj57J7-UTsPUB%cg>dfi+<Aj%jtrZ{a$1 z?F?CrrK9apxvt#CE&W(I8KS877N1yd@$M4$N?Nhdf=kTxtb#XjCmwQECd@a1XPWS} zd{BC_^kZV;_t@*O(BxBdA$xa`lhA!s@TTt_&9ks@4-J=AtG%Yd?eRf~UWnhGoVS9x z!Gt~w)hdsOpPtC%BON_7F3|W50&L*eosUeEd30h<tUk}6E(-kS*ZDF`J*>|K7-v0i z1xLbo@XWqYw6Ja>e7Ic9#~FCpxZcoT7P=tF-r1e6oNuZ}rKm`6^a30TB-rMbfX8<@ z$%xf_Id%=#n4;)ucTbe%tP35z=1{2z4@tkSo4-)`;lgCbw7%W`Ul6`vn?_g<{YuZ( ztzBME{qay-#yXEndQF;4gr35Yeq?CqYhM{pmG#!@n&(n$*ub6{HAW*~T82dV?JqWB zlV2UxF8@j#>)mMK^{6@#THj`?bnrg2F#P1Gb&ax%pu)4Osx@A<y>_06gHcyjI5fCe z=9AM#ch~L;IGkU=W}2?Ttw_M0M3)ACfM=-7^YMOyNR64+>1h43j4swAtVdp6H?B#m z0MxX)lr@!%>8>XtWObK2jg#ISR5jGMPK${W>2@ir9MqZi%#Ue)qJhEAZWj^GI7-UJ zEO1-9I^9a%QTJ|gm|KB4Lk29%Jb~+OZG*g!Hx{)N;8ehqjK4}30iZ{_O{qfBO<*gr zxVm$2L$BDPQ^CZnE5U;YlhD0buKV^3Q6k#OtIX2z=gVxs@n#W>bA8zBv<uxje!2O+ z*QE#`W3vpG&F3FKOaO#Q@#miCZ{fwsp%il#=NDc8H*~A7?@47F8J>O5u32G+*!R$n zL?%vxD!wj^yML%v3*ln7sZ-a<B=`o|xHFh<49;hMreV9jsHi&UW1Q3}dg$mfG#6Uc zGK(8hRb7MAKp;YDJN8YGx^MoS$9Y_$8i%o?z=h)s2Z;bpSgP&Pcm9oD847&>9fE@B zeNSFfS(2^A0^UWB<zWH9z}~RDj`Mb@nwBNa_E4qWH{6(3cHBw-d<7HokS6b<{QTmB z<Iejl)bC-yfMA_^0~M_}=j5I!qBM$X+T%z_;<Jd~hjcf^fXOK};#=6qW7CZGw|*pL zyXfV49X0@G_#MEl@Th4_dD22eq{M!rTjqUvCO4gzdEsGgHtf>;1;_fkGT<bsc}g)@ zoyOHmBK3T5dZy@`0|DIvPEVG$_uy}5fEGIH<t*4<Ox*i&<CL)BA^~j!mXt^Ydy#H@ zF79=F@ib}tkD(?YZut9=v8Q)E;}Zr%Qu45f0h8>*dQnE&XA#bA)bA&nn^(|6apMYr z49+&+(r~`16!*Fne{)#};j!Hm5!Ha)ADH}pVJ4ES%0M_Q%frna@#xKwM_yQwPK-$8 zU|lmsON`BY#b8VFqk+VL%INF5OuqVTCdL8V$V{8)aN?QN{Lj@}eD+_^1Mt>|8bb~i z@++IxeCD3k*7w<{Wali0drLXBRvxwRDN!{;Tr6c|(mI`Q3`b>d-aj|yG<!v+$(@^R zC!x`qF-KPC#I<K-zYOZTExQvP)Uwf*MGQoJ#2+tx`)2>{xxl?1&=HXf^JTU*Ud%e! zAF5dM+ETAXHWn&&aY3FIPvt$UdIBKdqhpW5I!yj^^U-gMFi`1}6!pGwL$l%z)0Wq* zH$NklPuD7*^8BGY=+|d%UPv;BzhNN1Ouehw6?RXlc;h*Wq6jy=?rx!yo~%tVLm4XE zOL`byCo9{5O2Ft-JU0s9Ji;*4`XlUXt-P>ppXC!&ruNqg`JtV4koR)Ld#65)K4)fM z)N*)rGd}iUluGBJ(kLpoxS5}<e`qndwroCbZ3n1!t>x37{AWXpTiZm`gImC?k<WGM z0GnOJwc$OZZi~<41q?f#v@Is^&rIF7l?a`5ag5(B<6Dn#ejdUd3>AjhkY2#hR;g=P z8Me3ghZ<2jmsx++G>d$hoGyzY+fmwGtH65xyKor!kl=tNDVdFfcP$J-&0)15+g3Hf zudUIa4helUX;DL-yUas2wa&7w*p*u7k#F;gsB|`w-&&6hFaEZMe9mWUX@vD!U42l{ z4LQY!KUZrt_@gxxG*Ovx)nJI-WbP#q?wAWm6$J+SJSJih+ZcTh&fNRbxl)?*0X%iJ zD#u!BvYCmX!Znv0N3@@BZmd40Gvs%*3IMg=I9OiQcwB4f&o0^9(+x+6ZYJ9(9Bz=Y zk%UI@6F;Lb6>>C@m;r#$>l2^;LqwL7vxD8$Sy*CKb^db)Kz~IO&|UqcN#%BOz4nDD zu?!g9dKgb3T8dOuB`@9oPNA`j48<t<D(V+G_ziIS1Dob@qtz1%0q@APjcIP0zS@c^ zwWFwtaaBliz2h}`M`xdm^U;&$GpF8#IkLP6(lX=zzaV;Pdgm~5SEV*Og82{6XuW97 zPJ8I@t{9?3_<8GTl_)YrVL4l#INyPN4mT(Pk~_cto3Cbv?|gYaIf?7x8K`;3m&`-` zYd>qqzI<$Uo5A5ML?=;S`(0KFsylF|4}(-&3FYbH#|_86q02*3V!6y~utcJJ>olBS zq?S1|l8{U&<9JGKYYWfc<VTz&nf-e#6`kGt=95uJN|IUb_3eJ<NoiD{F>*8Qz5Zh| z=kSzzkxM-79F`l8z>R*}Aamnf2G-+z!~V40U!g|Ixrb=m^Wi5ud3FOCkzpI>(k~_h z4R9cjhK-xQz$DbVw^FoFE6rX&>>Un$)N71Kep%yV4DDL{g_G$rBz+_bSch8F3h5@j z52lwYoof=Xd-`JWzqtTF88!@vw>i3%01<=Kv7h_lIp)!H4lz;bgvcNdRBhBI#|vf9 z)gt#czAv(S1@Q2W`r%*W$S(UWC!DSbM!%%WP44%7{mbz|z@cOzd+`T`yd%|uo=xTE zz<bC-%jtpAaTZ4~PVn3)v`LBz5bd$YSFrr62C}DuqH8WhGMO~rso1rQ)~9on`i9KK zkCt5_BLbnWO`p48L0E=HaZ^{TJ6%j&lQ%C-&MC>fIC2|Iq+gXl>)(?p2@5USSk#(Z znqr-wA@r*Hsvcj=5y3d#GxCM-Gjya#yUqLeE%`DDmQ31zufg>CfTdDtetonn6VRgQ z?ouvxw$rUh!*IKW7@Z;1Y}|e*yqpfZ*78)?2*$mvdn1s3g8ar`So>lrU3KL6aLLCQ z9p3xY2Q6jt8OAV0?N6IqHQB`Er`-NYowDQ^8_HdW<k;m0Q8o9N#t0r?4{1Jk<4d-G zLjmJ&>-hVn;ybp$)%Lit=CkPGD(Y-(S0(QOSufXHrWktftDKji$eEJ-lUF?~8u&!K z=`HRaANrjgKRG*BrE}U1^fuxl&svrCskIlgC6P)`SHz)?`cYr!fFOk?@q`8v-a^wb zB@sVKlh~8stI{?*8krSIz5lmgmg2W^h=MKz`dZLyJ#RCdG3kK<Qb?3lB5vr5lp*dh z<2NQcMU}AdSg*#~u<veg_#!;)QkoQfKp0~x$1zizf$6L!JRK&ZN{p0~p_WyO8RC@L zG0cUUg0sBu5BSo2wK|ZQ!lYScKIa?9Zty|%LT)3z!P!hE&zR%G_&d%ql}x?(uRDYF zB-+oQ!w)ZHXloEUS*$UgInm_qRh8Ab^urtVY3<bVDIZn&kQYq7qm338I~VIf@`8NB z@pDt!4!CW(+(2x1h4Xy>(ju!4VxXm)rXyZrN86-EYPVSr>;dq(K0GMw)zBxsDTS#y zWp#0CMs)lxjaCy6i`JATn)Xn%3iY{IWlWvqx{)6HP~wB)&}ct<<DX|K?<%-c+Xio> zg6`AZB;HWO)O2(247TR$<>HAEjVIE*ThRef_Ot7l^$03g{Cvv`V?9;YJ=SU$0<X(F z{wa&rJez*~3%r+8(|5rI)9^q<WC`s+GsMIeui=MmAoFsI%g-AL23GoS`;@up+w(F( zjTB)yJ<qRGkOh?6Fc4V9pD{s`R%b@1Z|2Co8mcqX@vevRXPUfuLOsm&N>4UgW1>{6 z`i?BJ{7j9c1b<6(nKJ1>MIj|a{=7F)lDsk`XHbWq4>Rn8B|bw;!b!*ISM*~pC?UK< z$1WGy^Zc<EGh!0h!;#VG0VkVBEU&p{^)4BlXk`<Puo_Hco{I(g13an5d~ji@x^adh zAOr_>z3SVemLu(9)6(Ugr}-BpPI2QTI5rNb^F<9g*<U%CnQyyVxCU2Sh)hCr$coyx zi$tcw$jwQIB2PAYAKyqqT6z_Utbz6{IrT&{Se2IETF+%^{ZhufS`<fV3?p|mS4}&y zK$&J!!LCqxUJ20bRjLzv$Zr}!2!j8tEKf%Q-;zI4q{Hb$LX_56z8l#|X{QtFWk5u$ z<V*-nNO&X?W2vtNv76R!AQT(-<;;EWy#pbep>>l?q<1d}ejj_L92L5vrz%GjkHzjX z{#7l#FtxP<J*H`C!|)jdt1vIG(8W<YvV{h+;j}wpFx@=dQaS@drTH1!kI{<kDP&u9 zo1UI0O~*~7gyNIJkO^bS7^h_#iNOT5d7&3rc~w;`*@`d1RFPPPCt{oza_LR)fpoUy zkG*osLw{VE6SyJ`WJF<OVS0YtqKC)Mb@!Mj;(9lEwThF53CgB<t>7-gb$kD<Pl5!q z^)D%gAvIQUnQ$elLz<a09rC^^{+TlO;_rdU?AqEY0%0Mtxo1|tJp>B7<Y<LeBatKz zj5`v^mH2x}1okHCH%VP6PVHS1IrV?AvX-%4qAzcID?KP^F&OpEx-!Yn$fBoeVVF*$ zjJ#`^@LKsxnu#~%&Ql2SMCeuQ;6ysu(WH_c-(*w`vLQG4xup;fj-2z&6G?VoKMfjU zkDc!NRbsGU_<5HOaw>~jHU3Q&k=BO0&f{xllitLee^ScEPwU?o>q5?32*lWB`|~^_ z4@ZmJSB7u4&33&>DhHSvjCaMc^nPaO7d-yi?m|1t09+`JBoPI=_MS~xpY;x~6>okk zMn!kwPHSiuAhL$uQo__$51vLuqkAJ?0K0bc)1XUzc{@yTEaBhJBMZU<Qmq6Rh2<^W zMdIAa>h3|lX{aYD(q_ypOpS=Xf_M(T3Tf=?Q=)t*9O|$6OD<C1w>s9}ZSi;4Ov<C> zr(Ubo=xx(l^W5FcocLJ6Tf=YN+v*hq>;fyE);j#=(MZTA64E>t-w)=6v`EwK!-C`2 z(4DEn`*vf<X`Pi@c%Wa+L+mK2{NXcVPESfzmj3Ta`}xT3Mgvyp)g1t!Lk<%~6eWsL z4wM=t?V-N-N(musX3cSRN97`$aV|i}>%o&{@l)A5Dc|nCvt3)A%trnZ>k2t-z7ajW zg7S9fL`kJut)Zv9*p6~sO~=j9Vl{-VA8Z3HoC^iNERkY<XivPvxqei@>gH+KiwUQ- zvoA5HIzYCv2q!A$(_S5|f83V7n>}vImOV%?`KPsrfxw0q32%+4*mu0XJgi0hlzjNe zi`j-b;S*(>zlF=qv~$7%Y$JYayJ$4N1o@Pch%)XEd?;#;=GFUN87H=Vj#vJTaG5AK zNihj=L>F~Tes;*JfM?YQLvwr!`L*6~9~}ix$-!8Yi|hpF@!yp|g><o1JX!RXpu%S0 z#M69?W~vEv_PCjK^<|2*?MUECZG4c*V>?Kc*_v$b#eteU$O03f(f&fhc=$WJYa_3U z^Tlx&VyY4AHtG8gpDt1ArD^^i-ph-V7haCm6sau|kXR&VNwo;iM}w8qN1Q^{jI2&W z{99-EHuvhRZq2c5bdq_(OIU`K$tB!k>w2P07s((mwybDVIen&VRopk?h@_McqldJv zR3#Z8l2_35=I1ZSiFr&99%E_55Ola9o?-^&YcU!_eoRf_$53JVcX`tB)9|w%eB~i) zUjHGi`{hHmHKusJF|zFAp;?ie?U<@gd@w5ys#h?IZa+vS-0qEcp6VuUY?+n1@DS;C z&CFbd_E?s<29MKZAA2TOP2^$zz4|LLlZxY+M^g8co%MaH#j`@~$sYzL+ryZUg>EBZ z0dH}A!cSdp!sWVxv~X#!nr#8$2*ZFQ2Cdw|76vVQf79X%u50^>f+C9VAF6Ga<-dpq z>NW671SU+siU?0{nPV9U%zs4TD6UXY&?T&oLY6=>dI(*jUeV|15QY#L+gN;5{gW^V zs8{bDIAg<_Xd8x%mi1^=n)A>oOT!}xe@aElc^adv?;ZWqZEd~g^MfHX4_-AC1P@YM zO}+Ges)axLU>=)U%gut0s$SoaP(fx}le6L3)8E>0{BDc64c<1Oq0*R@K&y5nR5H^= zDc0F~*5}iv6ltJ}l)7l>xU_X6A+GuRDl)UPtyMjzUnb89o&hllpXULV_Ck&xa-<_U zb((Wo$DJhIQ&pKDm|Ss~Es49NFs&Q(iun2ig!Xhw@oq?k{WqgzL^Qp1r|WJSnz^4B zq;+YwG0zp$gEzj?YY)M0Kd4g&*RWVQuqzvD&>`n<u9#6R)NZ`|^4-NVqd`TSj@hN5 zxf^zT+pO(<(25&I>$LL}NKooa)3BTxhI+zQeE!W(F9DhxnV)0%($Qt2tb^t7F*#N1 zfPs!<6I|3I#>rnLS-XZCIX1T(<hLkUHvNO5h?Bwd!mVi-BPDCyagOnP-Jvh?d=Bih zM5&K(KId*I8pMftoGw#vBz!*2bqy#{#`OPss2bLgst{DUhezfpw**OBNgE<i@Di@! z(s+2Su>M^%!kjpfG5NuPE@SyR!+tzdtOPBQXu5w?v?&wXq3)E|is#C5o@2(e+g>PS zui1Wi4&+-wQVyusiV(V8f8N!Ul*VVLZiDTRQaa7{*!3RMb{K^15XnnoF`y16@IDW! z_6)^T>7k2{Wkzv<4Ua{N-yoZ+&HGRFa#I0_(%D6bqtfuX)yc+Ca5Is0VCF=W4CDVy z;-Br7;}wIM(ea5D{<cg6`%^#_Ei<4m!1pGkk>g`Gx2?|aTeyU>^@bzkJC1dgiI7T> z{I<`_KI$X_=L37|iXvKw`#(R7)*|E8elj@HH)#;5#)U{eJ4;Ru$)@OH#&*LeN*7Z7 zLo>ACUIG1M+8=qy+<b~X9%KRXrWBsFU&HGiBW35cG0;zubp~zC0QNrg_`;PT*_cb5 zsGfdPMJE|GoMpE#L))fCJ1Dr9?&MMbQ3vJ=aiGG3zPZ^v>@1!3c3cc>8C2&XdoarA zI!YC`b$Fdls-^wnE*vmR&kw(xzb)V2TGiCL{X)r<lfpZj0)U7UM1giyYs^|!F+7Zd zy9zOvO$7F!xUqa-4*knrY%et8-JZE%ko`s~tRLB-o)sJXT*v{|q~NWbk|g8L%GlLY zM;I~Od7|60Jh7S1kcsqg{>+&n`ulXW$ZmF+$ol4>pHKrBwbWGCM2TT-pbgT_ZXR|G zYuR|+#A`9-MX>y=t!NC#eB*92@3ZD-6Hjj;o8MqnRl$*$Gn2)9o2xqS<+icYhVa&C zNX(CNRZ3wQU7fhNH;1lLdUc7es7;|>VUM9|l{S`VJnZfWYyoLkjW}{g>Lqb@=iNrv zJ~|Hau}coI3~+Chv%P`ent@cV<h%(-Pgl1z0x!7bnaqp9KvLw|(%Ht0T{rhuHan@0 zO#~3%agF;h{`%jQk=A+d6Cy1;qHjC<FmE=?PWwXcwrEyzzB;dwJr#2>clc(`+;_<c z@+pQ|6}4aPn3h#C$Kh^;&}^RowJ1@^TzPp-PNC(@myWWhrh_ysb8hBr*9vjP(-wJH zcqX9~e?mWY-GZdk+}R2q<Yu@(%)9AIHH;2|do$$WZ0)o-mCEdM9ibw%Y{Cv}th#YS z5++KG<y#F$`kzK2N1?~zsHVt)@v<~#Rp;k^Ojoe;)^9+?2XfEw$-mw^E18ZfM$q<K zEiLAODaW|+B972W;yRl6eCr{>d#^fv?Y6K<Bb?XL{RWfNG}OSh<q(uY%omk1%<w%y zG;vYps8Q|OQ3G->N*By2>;d700WQ?2^Z7Nz%jg5A2a7KB4}=7P#N>%auXVL7wHb8^ zyYc!4BL;}jXq<|en5z5fIh=);yQNdZ)3LK0x>fnLdiYk|zi=JIL+#QYlaM=r^sBQp zr7!%=pT0ihSlU;wdizVatWW@1Dcp)*WFV)$V+q}2dmuHonSOOMxXG@avIV+3HxU!; zNj7`NDibZfocnJsAVoLcij2ERb#$KeB`=#ZC>&v9Uy`BuVm%><U07R_TQZ_yEvy73 z0h-^!k*J0CHl7z<$^Eh;G*50cDnm-5NgP<os_eL5XN5%IpcD|lx#L53Tw2QT*;O}# z>#wA+?J5P;t%HTyvg@(i17&rgA=Lz=*HK+|nBX_}ur#<|OYMXbt?Ob8GJnAJe+p34 zj8B{yw(QSXT}b0nOPfwhi=<R*gzp>U#f?F&Ut(6b_xT|}raU32<9O3uKsPsB7H7ia zsI}xHBD7Qx14Yj54uC~@u7Xs~h>jY9HKlO6C(%~&CRPNiHcP3ObSwKnFwx)wbRE6I zaO{aS;|7mcK(%%L*XV#+HcDGdqQv9t-6*6I_bE->l-$wP7zkHa#X*Nib>WP!#K!CZ zL4K+9Ft|g6K>Hur8$+ynN8^I_j=>Ih1;i+zFp?xd0mlL4)-y<*T=_8y_Tiy?3MxQ( z>7>!0{{v}DG@|Yi2Cch0^MjHii+Cpy;y))FO+rzliKkyV2yS4#8&}vXRfUKC_YJ8; z?#iiDg-ZVhg~IUoA?TPfJY4w?5Ehq>SJO;ZlsD^X|92Ln*a86e0@w4P>S|+9tA?E1 z4JIl+o;h+w;g#7H!@r{*#AdcLR01S92%y+PLL_$sA;!c+Iy;a357nGctN9<QIpmWN zJ^f)!jMUu^y#MnL@bk-9iimxB$BMJi8Z)tW=LXLO4CKLkd;5E0fc=B72IHHLLc-)7 z<wa8efaD+=(;#*9JF|Zk|DmA@28N!L6iYDp4_2R4EAkm!Y|Hdl{qC;&=h|R8R!L|9 zs^06!6^XZQ*4B^K)(pV+%4x2wu2R3vQM{?i0;u*KXReXoDk-(h=ZBK;$LIj`s=xSk zax$iDTS|S0#!|+24-Zq~mrhNpt6I`i-GR+=$x1pw<~gR%P#o5aD=Qto3BA&uP`L(O zg4idqz$JnT*_nfGD@=NY<WMZMY>SDpF~a{h(%ua~X?&~(^p~-4^Trh*6IMvnD$xTU z9VMkS>@bK-OayM2_uM7ajD_y*7pMGad_n-<@_*F{8J{y0|Hl#K{{uRP|D!qp;JkNp zD7krpNbo12ay26psw`0w!iNt)75wfm!}G!9Wc?N~Xju67?_cPd3Qdl{t5dP%Rf*tM zQP45TA9M-<lk@J6CJ@{_N$5~jl_gWJ#k=2<v`bf>61enR+r{SYV3OSZ$x#6%K_5Iy z?Cm+QJb4{C@JHQ<%}qw88OR6}e*e!gT*_o9c{xUM@NrOyjMcHWZWj9epBZ^~zz165 zWWVCa_}2<!wV-=YCb95qK?w<}{|*8QY*IicmzYaVc6!y8VUETRA41^z|2Z=XR9^UA z8Bkb=9dxH$u4em-y>*+xjsBm1hAIi4-jk8+uBa;VU6215l7$B0nZBd*0aqY}<^LMO z^@^KW*02d^cx}kI+iD%}{j@H4jndZ>^S$HsHh+4yVLCYvQA~1tKcAC*87X&FycFHE z_nxDM2qE`S>r9dEsdKZ=&K0q_{Oib<^z>b}c>@ahRjRj7xcznq_5e(C1(!S=S#CTg zw?|k%1kK!MO|jPmy8aE|gJjKH(|x#Axoos!^TzUOgYAi$1^vuVw9lauyJ$%z;K7wU zJ)vf%`(ny_uV?#*EuJd%jyaGO66J>d_`3p<Y1l`HOCtQzQs$F&bL)b$)kKmOJ4*&O zXGz1yq_yrRq%zN@r>3+TZF_(#br@(lI4^K{;6buJ(A>NKk;iWIZV94%$OHXWfkM&K z;pPE`2WeRR5F4A1MuUIUFW^XGy0<kzfCYR{4AD>Z2&{l8>d}cau$IuT7K57l(q<4~ zp+Wq5wzg6}v_OWta2C><FMEf27=na2uYiBd%$-Aok%?p#LYYzSWqdbnWzUhta6<=x zH9=oxH(+~aVdGWiwA}I}Qhxrn>KpClFx%yF?Jhm<MXcD{DhZ%3DWU>$b>IhJGLfB- zhe^9r0t%;`&j+$$`=_RN)6Z993yAk>cLFy<y@UKxw^PLZd~|A74F+cWtdc8+&i-TW zQm3zK8VI%%0jHZ9ugK7qi+*<PgCJ3AaTUB-jR+7RLzwJL;bLEQdDM#-S^Ts8L-e+2 z`eWWwi)Y0T<=mFBEqgorgm+oo?BcQH8)8s4EMIlXjE*%#w?xX;GTt2K-f8w1VB>w) zGhvi_F7J74TJ!Nmh;%YX))&wM;}HcPRR2(hyg7`=nRKtuBi+c@*x~SwcdhC3KB{%O z)emC@W`@~$z{zzv?WWS;7{svSSyp)5yCQ%8=96re?f#0q_pxiT{%kmA&MDbtlW!%u zzeSq)s<)7+0=WCRpT=#wyIyb-v)K>Ldb|Rg$xOUDOdZ$r-j?|5>H4k@?nD}Y?hM^+ zSiKLnfZOg+-nxL%)+L2E0L-jaq8>{$c2R=@mf6}j!5jWG(ED%7=j#5y)fXL~775$_ zh5W)djdA9VOb*c57%v7JoIEzRN_ASG1zd-!XX~c07N{b|fIN4m?(%t|cb_h7w$Pz4 zFX(R!lL3LmTl_VI<gP&IN=rN}71vKf1qrM+<^5YFFe{N84;g1FatJX1LJw3Hs|lFw z<_Y)zgyO7PeIw;80zJdw^z_}g*Sp8H{Bp8Vtkl<J0&Z&~gM2kL!k6i4TCD*W$nh3( zf$-7OjSfjwCatEW)m0>7lgyFt%w&)3g2!d?Ao6<pjCLLP*oUVLam1AJJDjxs(qAr3 z6Ies4kKDM8r=gN^@0!gO*#WARQU78KO_RHXgtu%Ln7x_yxdl<#Iy-+JY(j$j%cRo) zabFn#cqN-3@M045nX%o>Jw_Vp)DyD-z2v{=VeYoI+9%LvbAdk<N0tlJqYV*XftkeC zL<1&0E|F+s=W&dA0Tu4mFncAFc1u^-ywBBA>cDpu=}pBC2`Nd<XNRg3!UB_Sdt1wS zL1S@*j3gN0Bon<eGg~LuqJ=f&S35q;a&L*#V=h*0ENHO#oE&{LT6f+cUS}=qq_Q(X zxV}{I0Q(~o6LFxS&kIA&R|%p>e=y(nqh=D222gT^<$T3rnYXr+$=1OERIv8rygG|| zRI<y7#9mz{tZpCgwE<!248OCOtVUaVOci9OWH}I+#q2AUHUT^ZRz@Q(Bh&U_{_p=o z-dn##^}XT3AcBDyq>>6E-Q5TuK)O2y>F!QJK_#WTJBFcafT6paAx1g}q#NF?==VMU z!nw}=iE9S-X0P?Er=R<#xE$5Cq}NO@9EO(|i%O+(73|3yZaltc-6p6zo6PU~IqBWm zLR%4+DCvKKv3+=0+ZQq~7m~D}p6&wN;<K(h_q=}*<Pz{L!fsz%baZ;-NE~er7uxTP zW$S(&5*P51k~UL9go|rwYnCi{d|UXIn=Sue_#TWO2@FUYPM!ns7&Uue0pN}?5jeXo zGf)|cpaoLHxLrfKFo#1uFN4B7sPbiOC_qaqz3kk?SA;<;fRg*9RHK*=#l_W`*JT$D zMbN8;7c4K*#?VC7UqzsKRF3iRA#*G3OunFAdjR|>fg%!&7nHD@qnMl=W10SCVD2X| zwZ4{{>GpC8AX@c3nwi?>w8{zD=0e;aOsta?#wi$xZDwb$2;ut*IZXfTQ~!Jx!Dc%B z!_P4-X^=9LW*@rYn^vL=3osZ5gz4F<ae0A+yswudXtTPyx<0z)M{V*=QnIp_&OIQ6 z&+&H;|K#L3fYH!pYkGHd)iQc5rx6MCXJ@0Fjc*<vSu{3(N|&ap<y{0w7x=?MjdOs} zH2VG`-2uR<1FV||FtlzS-zdQZG&7J2I93@)HFq9ghoxX;c7tteI8R&2hjbeST<nJa zQ(cun@RAl6ms8edEV5wTYnU)JNPEXSJwu-?0Plp;QZjh=^df(kwz>)8el%EJFIY(q z7C-t7Kv)3mH%2l#>7W##qzxcm0B~Q96++*>S?dhNVQ?aOcGbHKuS)e|@$qqw?WyW3 z*MRwG5}tJJLvd%ENaa&v9@uqr!G4>EB}8Kwte|e{$wkl=1}0)cl-eGplV0We0~O*F z-ukqPsJ2v!b)bK4xuPa@`CbDM%!Hj(1NbmW3_Y{8+b_YTO<QC0e3e(bL&rz^U@$!Z zrIhn*X~!&2Unfc-HYR1P+PLjI#pT<)?6g`tL!e#{Of5^2#1Qkln;&%h_z03#z6eq~ zb9w1qx&H16kW+olKw$}3W&z&n^u2FO?=F^nKvkdp&o!4H7@MyDDiT&kvFhhtUt0s< z;d*Ar82Ar*{BYu2$OOBpcys%(fQnow5xl~@?Ni=&8eBg%<4e2wJv|Z+P+<^f^V(WH zBFFdC1prnk^7M1DFL$g|Uzjl)@uw-+M310F=HlXQC#PWdu_i>nT8Baec^r0d0}9x$ z=K2c+>YXzHdorY05`&SN#`oFZi0}-+AFZ)LL?tCb0hV|l6qzZ8CrrL4Aw3nj^0|J% z@QDQsUM8yDOA9%3wKh_bk&?2IvLNKT@%ivl2ql%*{hk<%{nsew5CCwDW;&?5!juP4 zD&Ss^q6nPqEF6y~IR0knk&!w0WtSDP7*3A3{e?+DK)}Rw$_dtbx&x}8++vZ(2Jo(w zUxC+A0kKQ(+&^*3dAkk(kRg%aug6GiFQKd6>b7~_%JgVNBKYgpxu<f!<Il5>qbHA6 zg&OBlG-*f#y<=`_Kgx5NI{U=1dG826##D{c&G6c*9J$_%7>#VZbe*Z+8;^e@fV2kF zn7;zLHkLXcgp(>4J0I;-RffpWN%!S<*;72A9y$R4M6SH~si(6R0Z<ZqX+gIGhaoxe zZy=?%O5va{amDZwHk794PI=ajt2##?#Y9E`5H)y}S-ww9^&@p2uhH(NmeW?C%LW0J zw#=16$`P@BQd{WwF8gxi66moL0^piL3mUziaZz17jI*7~dc}k>{|YE#-s&mO5n^jw z76C8`4d>^3PP)R4$r(VCx2iJ{eL3^`g?6UXQJAPHzRcjB;Pk&-0GTGpnP%}QCyAY9 zb-ixWoDy+y75**0w!WUWlBi-(^zrsR1t$jFM>n$`@%J7i=*ZDn*0&GKRk_n(4H~yf zAMdIb2LbGSPXa^)()IB|9U63btJV__o{}gHBh`$IaJhbNZ?|CqE$kj1ZvTmk1qeKh z>sy8QMf5p;oeL$LFunLqn|={GTiHcLMMVITl(1=@n@cAD7-U7G&nV~@<uN@|r@yoj zKOMj{2z5NYDG_*Zqf@UWfJ)gIcY965CTHe$RF35dt+t#5d;bo`0e5mHjuBi##w}p; z0m3;zl)ucv+Tg2h^C@!xpmbN@?~w`@$(6HCRA^%3;kfQhc@S;j4d90DQo0iSR<@5M z&2dK(2qu-RMrs++n;%Ki;G&GVF>VuGP@CA~Wn=)G&TnOE$bkEB`+~;yMr400cfM;+ z=-ZK<V{S4t0p#7=9Kj&9HRf}=sKnHkoTYI9LJ}~HYchnr0?6^)ePCq%3T1i<nx2uV z@%F&UHMhe-UY8g-K{sSPLNiqSLWVZlxA#Y$<7_SfW2pQxIf2p~U@i_wpWQMC;22NH zVE>UZQ-agiU&9r)R>zNz>9rC??3Z^%FCN+srmd*gssVklK(3_WV&CBAa#U*T$@@@E zQ!SkOmoIRV(swiCu@fX|HgVA&6jpPB3KklJR6S6DNpPA-+{eyG6EXzmVJXl~vco~l zsKcC5kh;}sIPr9aH_V3D;{BMxv1$otWSn{n!E+pC1&}{>tG#?0FhP~ZWE{`HfO+!y z>dK$}IO`jAN-*m%lC5J@=V%sY3n6Q|F7|1x2e8A__UD4(%HrbUj%d*Zxf^j9SJz%= zIRD|wrE5j`;~zE&I`1HnlVH8X*jTxbznN3QNrhxtt~v+!7ChITJ^0kH>zhSKY!M~a zU!%u6|2T&-%0@E8t8#U{N%(6fq8Ba)bP`pjot9|*(TmAbzrn1Yzfcwuc0VZ^xqS-r zV}!Y8U^sizjD0A>2gd+_5})gP-KHlyX`82_EK_+c6i$V2c`Mi0eQx)I+b*47O&6iz zF!x$H&S;?x>OIYEKFA_d`z4W^Ysw3s-AdL2J(jOKxTx@qj$5$*)ej_Lc`GgYAs|_4 zfT&s`Y3-OMYkO-+nB{~h?V2$PZ*{%DKZx_is_iC)P>@nG)T3_*3R?V7ukl=?noq?X z;^<|t1+sLJB^mQwvU>)gW_*nZkGq2EzN0e>qaC(*ubb)$v5Hy1tkj?%Rrlo<WC^J5 zbKQeAU$dw>-?~jy;T(74hu%wTrvj8WzBH!k>TrQ5UkTxb5Zw)*!;X=c*V62scXCou z1B*iqBgqC!Ga@eO02s(RI{Uepb^Rc`T*>2tJ(Vb*K%?gUwJ_mytvhs(M!iJO_iW>P zZS9%Z?PWC4Sfgdxw<Ew62)@-2FWZMm$1c<sGzg9t8ngA{DHlU++SDF@hPrH?%?^Qv z5?K<GQj31-wr){oUzJ&O{qoU$0kp~kA(J41|E`#zPcPP3b56Qv@sh&RkOD$9OW4ZV z((}W&@2}a(FA6NO%k_+a<}utXQXWUj;i+Kq1z_u_hrNGo*n8_a8a(Er1~uB9!~^ZV z81qk!3%4c#hXM=ojNwGe?gJnqdWjrb*8oer+l_@{Am@dBi)53^yI`Q+e!P<UE+@B0 z=!26Le+PX@00&o<qDfNu_6rt!LQN;B_1x?nS4+fPUt5O^eV9+AeywC~R>}ZSM3{dR zI$c_n&SuTz8YiJBfACm4=|J&Gj^b){yK`=GT~!PvWwUMy2{nIM5X0FHfUr@yN<%v7 zhKaU!(+?aoE0cGlnsK#P8`vLs(0;BB5c&f2aqPlxen%W=L^Z%XGNwQd^D)uHSqs{w zhX*ah>bk<93S0r&e%jcpAOO{!Ra;o-)j|cR3kg{rl!kmL<WhJbj#8eUmt{Dn6SnZw z^4g>Ek&(q?^&rY18m4Plc+*3(=|IajGJ;;W*0~MxO4FE3Fin{GR{%bERNHI+_!)bo z0}L3IWNol<64;r-)81%(iG%`eEB3yB5;f_yJ64a-V<CgDBz%vr8`;XGyb%;wk_E5k zde-MlzX5)-CP0*Yx$%MKbYq&8gTe9AZ|qgV%rgo@0O3RWV|-&_xItpa>=;tZ&g{Cr zx4|Cp)3T$Tf)Iol_$rxm<)=pf9pMoESXohg*u{ZC3a=S!d=OEY+`gj1Ta%~nwx>7{ zb@<bBF+npDRe(<+OzV28M2zC`r-_l~8yo`jMW96)2%7WUBs5c?!$AEUP>`91IMq8m zuYN9XF0WeX^U09R`|htpMrXKvvp=VRQ^3bSU@bGFEWZTwMN01o^!Bve9M>(I<1=FH zPG}}9>6MxLjxW1i#D_0qn-e)GmLyfQMV-A3t?m59JLWRj-@CT7K;%kFEJV;AnzvEo zIyHG2rq_T^Oy1KIs-TL)47h(fIqH6#KTsK|wCIJ7L7b2Vc8J*FJE5B^&yCN&Dl6H2 zg%GLpP_KhUay~xec?>$bB4m;p?6hSaAaK#t(rP#uJ!DNd+TQb_na~~<;RNg644lG} z1$;LF(oREhEKlpj!;1CI+Q#D~2*bmtO;DR<HMp7eY~{r!bf~c*hy>lkX?y%;f&c)Y zj*E^~30gq-7`~bMs`mB7`oYOw3PVpF*~M!BHvpIh2Dm;weRk>DDzNLFnoeKS((zH9 zEr?zyhi8Lc|Iw}r6UH2NwDM)L3DD!5AK!1{X{U<-a6<Up{!<rT3j#hqIE>eI4)C;u zE{*Xh-)OTJa9Iee9~a?96;bFpve|!vP*Iv}<8w;N_H|!auX2{GF{3*#e?3+iSQ6cw z1LDE7MhEG)xOGr&hepYL-CCEVSSv%_ooWHxB~ottO&hZ>0RgNSujZi3Lfa=w;}R{4 zeHC5#d;7{=Z4+I6$f6F*0CHm`kArVb#YfbA7gytp05FcABM&!|kniZXCZMfvoU1I8 zIX35L$I`L$wq7S;G>Nqk0i;~Axo?3gp@V~?z#P!4g{t0Hul*JOeNCI*rO_khd5oTT z=Ug|p-VGn12{*b27k_O@5e*8N_lfs3|L@jDEjG5&c+y6QT)i7wxLYQbB!_?sIRcsP zUaa=b?GIADrPZ8MyN8L@-2<M_1avS<T)s#MxZ8J9C*JH;LD4WzTLG^eoHR*R;B2au zNKZ#MH8Ih|>y)D__nSxuIdE$JGrR-gs4Hpt@R9d$NreMbGQeUvD&%AG+rL3kH2~17 zl}A~Lk=DygF#_Xm^>ntHnWS=(eNHZwo~w0b{n2)=OV<Dbc7Ij5{haqFAihqRAm*@7 z>B|~rYsO92(o9%im1Ll2uZU>mt?3=?k!EV(+G|clUN;KRlq1Kgtd06{Y>YnFH}OJa zve31(wa5K82!2009ZtwqIaLtMg&nD6T@2P(EU59QwfBaVJELO#Dpu*R{sADJ&^)4# z>ftC~2s?367PrWqqRAzhEBd^MKAO4WfX)#6+et?e&H>_HRsaASLqS``1J5i>g;rj6 zpt7t?AMXvY9wQ^w$*01<Z3S>eDT%SXMK6ktU8}3Z8-Bsb08FeB|K)W?iI$oOh^VfP zeT?2W6j$!h-4`_~qHqqB8Gc_<MN)_Pa&pzI37P%w`iY8+>cb*0@4gbIQB@?;Z~B9D zGc-Edf5!J7s0Sqz8xNSS{9)4uUe+r0JwVmqZO}71RuLfLS64Uv3mgKp76AnVY#2~? zFev->=Vee(qI)VHed0+-h<-engAz~vu@_^E;yP>JffC&nLg8UyZf;{4+<wPIjS}5g z@#IqiZHx3r8@<$!mG!aD$>K`PfOWtQ-W&<ZG-#tSxce7T4Eg_$GQzjN@AQ8MQ8F_a zMI%F@H0bs7hcBb=YZLu>qrw<CF$&SsW+|f7{WfN#`-|+4mVfk&Fo!xHn!ZnW_6xxn z9)^NcMT(21R0l|+iGCpXu6V*BVvYG}!Y|4XHAP<fE%lR{BQX$r=lDG~E9*bE&VO$q zk5c@?qkxQ}0*^rHK1&_FddUyk`^EaKH-;@%R{?keqi%i{3hAlhAK&TYi>#S4Sgcb& zu6(g?$c*!-|Hv8{a{s+ZzPn4ga3Yny!~2#_WdZ!b9*|Va?G`YVpnYAako`P?#)=<z z@c0nfu=1|9>d)4vi;PF2#FK>=d@lQ{8Kk@K-%Wq=T9hz?rZ1Y7^>Fq4v0iBhu9#xU z7R+BFK!gort<mV6QMG{|IDxrY&8elOze|d}w=Xjl{GpN6zty6?ii38nu4-)gi=sLM z>ry~L8NEQNBqhCkD{gtFb@7YU!kYZ1{kbfK?*8KYYijrS<kwc{%I%{rT?Q(ODAXY` zU<CZndMGIVF0d2_j(re`jP|NVK7z)y$np0CcgG%*>p7QFu45g7Vk~6~EIk$+w%`fJ zCzTePW}MYw0a0}d>8+2^D&Pb$=C;T=nfL9K!O_$ok7Q#b(&1nH4Z`7yCA^ayF8npb zJQa|(25pvg4+jKk9llGD7VPhu+?zN<(cmKuj-j3udO_+&-Wd6oL1vv*&)X!xLV@p8 z+pD2ujtPce*?E*QPmeKPmau8L_>r=tLowlGa!|@(alM%Q^L_cY$83#TQiC0ib?}*a z>@G)8+=(=)Y=W(X*iYf_F*u@6?rKTmg)^$@Rm){NUgy0p7vbL;rmZv;?*R#l&54rp zVhU)HWk~8K)4P>+qqX&(9YnsZwRLXs(*X{7RiW}kc~8sr^=?sljjzer3NPsuI3Q|* zta^jhR&^lv#ZiqGKyggRpYhQj76_-B&bWzGC*=H?==>{_cyiqq=!~RMeXD(jUy8_9 zyQEeEGc{9?KIevKhpg9!_+&Uy47%5^t=+S~sEN*VSF0)6B1>^5PvsxdTo~6cY2;Eh zaWJRiAy&|rS9v>4sY-u%w|>1Ww!EJF6UcrFQu+*>3fjovE;-7Ii5H`J*qVx=#<?TA z>KU&-l(p{jwldY346J9mv_2+LtP~-o>H*=zdeFX+CyCjw$mK{pnGA!Py6nX>^NG=o zX-~h85gzJzfVQmp(RW0wPto)fUI$~d$a4F?T!1XK;ZGy{dij^WW5p-df6vtu4nDME zYMU+`{Ma{RF)-DQo?Y6b6(78MN7%gplU?;@?Jx^&b^%*gtJsFKpfauZhL#dK?DF2N zp;N_0{goB9@HmPmA?nf5IE-^w1&Z_+l?ftxm^FWRs8LjE6A4icmx&qQyh?6y=~~(D zo_T!KUN19smf2tG0#R@H8Dics-H?2knjfNFaIT}JwElRC8?MdABSq{Zsi2akNnzR- z?;_@qa-0_2*>}ADrOE*of;S$VyVzaP8vS(%UinufBgc1WhrI}t(R<Fd5@wZZE+?Wc zjkW4PB!~F?ozmIw&E13GG506WxW8wZ)FsqA*5Qrq)@08?B56$q<qT*tOscH@sw(6B z{S^HI!&J{rywP3t7N?p^)eXzjzM<>FI7d3l8Z`{Vt!FXB6!~*5Fd5%n^=pfdu;HGM z_r5gUoy$zZS13ix#Lm+caeWjOMsxZ85@<*tyqunELSSDny(!o@E?br$E(4j4mZqJU z<BHU5_47<c-&|l~1k`|_u%$h;82Bq6y~>|wQ7rG9JY&P_tiHs18F$ISEU?_s4{sq> z=KnA>rYEXqXzm+(#Qm{_lU5cA8rV0sJ}qKV_oz(g{I|bv4b-Cclf3cgFKfn;KA5DQ z_1g5iJ5p$vOmCAz<8WmQ6p~*qN#CB%kmcCwg`S2+tsG9tzJ6Ys7=HVm6+6@d0WeyN zl1v&i9$FXaSRJXle0(JxKH&`m4Y<HM9AR^YE}7~76DB(jJ`|Gx#RMJ3INJLigxR!} z*a2Iml7|!io3}e#@cxlxjN6T+NegwAmZL&<Pftz9J+(?=vHh2|XP0ZeW}z!08Kr8~ zsJY?dErkwj`Q)<5lSbYw(6L*_53Jiro(H(}74L=CR)#nQ^DM^brdR})nPk)VX&dQ( ztkYPm{ySBw%btFWgbLKXjxZ1z(F-wH*-Sb2_u`z2x|*18MDlf@j%0E4J?@&G?-@52 z@N0_($lBI~ot8Fq=k(GU3<>dZ9Gyw*u^PaQO)4N>#_pDy*sUoYt^d?xx%h8m6o@?h z7+<o^0Wu_*Hp@-t2zxKdGZp1vo*r7{{_!m5Jg`G4DsYzQP#vO1ePSO12#`RcHk;>= zzYA<2kT&nJK<}tQ3`d%1ICUB)k>>F3RK`=b<(%2$s!P>w%mt<9`&&$pGwvLUmkNi4 z!j=zswvykTh7hT;7jvCzGqZ8!h6bYxWNoO^>x+67Yx8ODg!X0=L?eARSL<XqR9k}h z0O?15fa+_Q(w28tDR7ilh1#CZE~8Ic5aR@CINa}xV}7<;ZS+Rsv0e^JZO6+=hh&F_ z1U$!*z7-`GZvp6;y5f`KluOml#@?42`mgu3m#hCC>cT`ghDL_!Ks=G96CiXCt99R_ zBvYtScFfjl9I!P~r5TyaK<0PV+1?ja1#)n`9NX8d6s&A=fmwH!apENC<o2IBAblul zcFw|gHUWCsEFbLtr{4V@jXnH{v>;i};(olEq@Hq|8Sp>=(q}ah=~Fic@sPC``@8y5 zN)%{64c^<1d{TpgoWp9SixTTVyOKoCD8)fZ(eeOZNrm7gMSK`=K&LJX<20?XXh@4P z6cYx)VCL!FIH9I_qt&x(vWjnqy@5?4sL~G&8>ISryGjjrT3FU5KlQBA(}YYQdjfsB z;Dh=tNk^5J6W`kKLMCGW+J%_B&eMCkA>!6XNErxV>8__nCR(S&ueh}dBNF7Z(xi-> zk^OL}hK_7MjlCZDH0DqbTotl&sQK1pa<F$Zsx`(ik`(`lM7M^>k_-I$P4dw<UF|mV z!Ll#I{<85`3g>3axjRf+jH|9w;2`thbszMG;XmFvO7kZzY!u6C)XDkXnxBgK3hWN- zmYYTktvwA@6=ImCjzUDME@xZfkroymaa`DOPW!d2g*GZCH{j^x?e9D_k&gaRlo{rA z4s9b$wlWN1y|qD|fwR{R;pJZVWUX^VW$($#kq|cbR0l6%Q_h{F%pCVB{Wf2HPlQ3l zdW?L40p~Bw)#fp6^byF73k?0*<k921J_D4O3c;++lz^(No<gphpW8m;0wo!c@iWNZ zEKGdKIaRKsxvfn~W{UHjYH1sq47mwBhucRf^7C)2jQ31G0aI>!Ahf{kz%E}cLZ+ud z38rk5-l%+y8az$cPgb!>Q}qer=<+tX<0kg{;6qC2&5tJ!vDOy87*kbHq(u+!sw*g9 zYAJ00vpV0ja8U@Vrx=J$WYnPuSbe?H(WMGGrd$t}9OD@@Dl!T}otT=d=OJWl{K0`u zB3O+vmLUc6hWh1AR*`E3_%s2K)vHtRJZ?P`t68}GcLPuSGRZ#8#)X|D+ur@KrrxgL zJ`Ia(hu*%v7}Qcjl&PjT^b^IV`fbWsQe7n|h>=Ps`sxqI2nus{C*nbEy#Gs$tQuN1 zW+e{#sTcH{A%-!2v0L~RR{{7kTI|M(P*6bF>bOlkYqQw0`!%QZ=;Jl&Gg=k(2lxG# zV!ANndX9?*_T;pWK`qxl9JAN>MuB(1g79|>WhxEc*=viF^Q+N-FB1ZD-d62s#l;WC z%huNVV`*>d(hlquL;_h~6x%Tq_>8p}x`<NIDVE9D7?~{et!D-|1}~g~kw-O`#vM$8 z+#VHW!7=h=snjpiumKo{S@ROsM>050i4fuhLdEB7U(<{Ry2>J86(on6TG1IUu);Z} zEulXi{--hvO_X9lEYDtchIN@-+J>1KG1)8`Eev*K=2VDDQ%MVH?{5#JL^&9P^uqIK zK(@OV1~|s+fKPbN74?=Y%CUsXWL~!Ck?cSOk<F=gL!&)|N$or&oq0q$>Eq9?ez=(X z6r#d6--aYd|GnjODVauNQ?F1J9o^&IQ_o$~RV=^x#L}Cz3hRo578jUKk^YzFKL%Mp zT5A#`{#Exiep6wwiYF7po}Sk3$+strF31fFdvl}txFlY0cM81(N;NSUiHl%u;tyRn z7zmv$cOKF+?yF{Yd=KpYLiiY<DBro4NPH39bBLK6!rgV!*ajak8uXKSW7rcuU8PEY zA=1)NuH$m%&1p?_Xr}mlZOa<)8Xo;w9hB(o$@vEwfV;r<-!c|RuKRm`|7i^eW+fhG zz&%&G{j3mv)uSQ~+mTY8F6eO9+?PExdM2|OZDze@Blq5$WN=D?G>Wuuzm-lx42AS# z!xyG8zA`0R3X3{6Sek0sQEyq-FM4-u)c6}!qL(KIuUU^T$<hD1&-uE5`^;9U^`ba0 zX5;dy4O*l1#>`?@LNKFHXjJz`z_uh0E=rAk8FpdfwVr%5fZmHWXIe{5T|DpvIM1h_ z#r!ABb|Iy25Tzm9Yd^unQ8CSfSH)J?{g3mW?6i3>7^w9&@JR=MJkjf|gFs9uD=$@X z#|!YOEiTvodbtYQfU$hofSZ`0*GWl@2Xk>qQ$#GZN&V-`w#9b^pHZIBQ_l!-du53< zn3<di!YHAVtldxaBdH;t_m=Go4wR)pD{c>Wdj&bKphc4j=i(|OB8s7)zL0&YuI5&4 zZ}=IexbP}IsV|F?=nBkwk<)K?AiDN5OXuKNmEJAIf2t%NvF^C&7EtfP&Uz;l3gud^ zWcmU~pNZv5PdXq+R>c&lC&bea1CbEfNEx>mJ^2LgSDCu+k6nSeKG93dCQt`bYtVB- z=ECM7E^=pc*e=H)6Pq^HRO+rHkFOkLeS}X9<ZWrVG_v0JM$~XtupwwnD<N6kl7{ZD zirZaakHy}TjreP&+5OQ9pMrj?%Z|O%BEYkFZCdnHu45~_Brz596JSAI9Mr|T3sE#o zp&;)%kS@<U9Q-Q_vyzFs)W7fPmh}C)SLh>5vc9f02e*Nr6Tk$HM<1~;=+YME^-AYi zNil;mK?8G4%j;iMXG|tYB3HUm?jrCW3tgt+#1SSYs^W`14Xep);Y;PVtGa|R!NC>1 z(4JmcRODEHPh{%6)Zll(2VAH17*D#Kuy~{c-LyQlwBtXGcT3Cp!j>n@>8Re)iY=<4 zWcGef4X_@xZoo2n8@##2{Bzzq^L117X^xI0I*1N0I=B=3v3^xY(BXbHU?tEEQ+{C0 zl;U|M!DXAEvwtwNVIZyF|MS`hy?$ZJXcw(N=)!jm@W-s&e2$71K6oOGboES{QsQ-b z4HzqeVQeOI*T9BhlCB12uHGH1mnzct^!<eO-x$?7<W|hvF1}o~Oa6}+Y4k+-)TX0| z!uvwfBp7_k5fx7u4hSYN7_gv2|9l+Y{??m4L&F^e0$g)vAlMNbap*K$9r%6s3MeIF zFK%i#uB>&TwBK5jVc}oEZ6QzelUGf|8|hQEq#;%|ZlriqeRlMF1&&TRz&Z(V=g-Cr zlY>FnfJI%7SrHq<l`kT&;(Gt_QV)n3-471{#qFhg6sBfV#gF(yK8S*sDUQ*sQ!k%# zw2a|ZTsxFWXWI8<#n$;aK8KW(88TRs_Be30iE|BZsUcbs&0A9qP7+!6fdx9i0u0CT zdN9di>U01<+(8eD@PDWoa~V?vzwUJh+<Ycu4Nq1z?SCFyYGNX)8sI_tRm&OZr5Fj+ z>CD$59Dw(XyIjq+-7+g^3@q*(y`dk(I^ftnE#f;Z`guCr?;DeL$<T4_Q|AKPnu9nH zqC1+s2Fw1<_0UQJSVvWcM2tJHR>-N%-u6~jls^WJNkB$Fvtf?KX%Qjgw@?W;c>ZUW zHagJ2<}5tby5c<U#mh0ouDbfJddfV*<gWUl<tg3N44@WI`&7K&d!7uGko>#9YN#z* z@l%Jy*={~)ys8ZpWRgG6WQgwGCudWQW!+9#q|TQw6qW*OZ!9Q2fL|Xtwl=uH07uZn z$ug%DKp`#j_#=-fa0@$>e#l<0dtR#MKDU~$ON|xMhht6EY~3d&{MK?SQ;+h-W6}(+ z4ej~jBw7dRG<NEn-c?upZO2cgA)G!ujsJ^po>aX-D{i0*m*UgzIP_C1?uB3D)8|b` zrrG|4_YNj&r@;wA_G_xWuZsiQ9G+BN;zei8Gtp(tL$(wZY^6%9aU`wh|B6$Yg(#JV z1ul9cva7y6K`6=vb~HROaDu!z+0vJxVC%7<_tu8qoZSy!E0;1qr89+}UQTph*H^`$ z56TyM|63%<fs%UzZi=tsFxUwm`C7hpsK6}?0v4aSaGpuvY&!mSa&$uH?Gn{nOIiNh z{yGiQf4Klh%i#X|44|M2Cl!?sP|!j{{od~wSIe_E$s3!Am0-O0pR=2Rfp`$VpUL_r zJh<+s+k=Xm)L0;PR!`@El(@jW1&~jjjvZJC_NV?!-+o%p5w-}nwqe@dS2jvH88vue zwK}(_o^pA-L5{54DyxfX_}^0U|7jdYv(JbIreIL(f{Fp0eO1BUY$|2+zN|wgs#SbT zNYQXa`Z&&huWEv19q_Pw&I9XVHr0;T$cx(eU@?P;4{!15$apRcDhTu1!LqlM^G@HU zx8t4UvlsMrkj83m53({7hYELW-XCde3~4Gl!4Uo%>Tj5Q$n;f@w&7B{TyN&<r0YNE zSpIVyBNItb=u}n$jQDZm?J39|(Yut<*CTV}9kT7Q&0|$cVjie%45C?%dog-Vbjoa) z)0~uXZtoh*%u4Q9(x`}mI{aBe-HKiAH+Nbglk;MGrbwdv!Ee8dNJa`LGEg_TGzY=2 z3oqN3tlj+T>+2gz)#|oX+2~9*$!kuc`D*&?x1uc;W7PlW$3_C#lZsZikbTxD+)w8B zO^##++XgaH6&s1s_FBWU#*s1{g{YVT<e~Z@o-6MRtCkk{r~iuA*mq4?ar>}x2YQG# z?JXsF93~Aj&SOjiPO&#?N^f%y8#VUz(ra(eS>p3!Pd;_D{0*%L!2mGZm0QTKWfnEu zNSaH@CG1F%j7;VT&F3Mn$YS=PYD!p`m7|e~7Md#mOWo=Y45Ad-vpj=g6!SWE7ljr5 zTz{*{Bf@%fFcD*W-nOM*tk(MX{)zdqDt>BIR5Zj(_ZXb{J3|*USvxh*SKq!8IvOSY znMZCZYxTeTYK0D=SV|ZdGczlPQ*9}Ltz?xZ_BtvP^S0V8%=!QKYwFhd_rNd03%Y)J z>eKr2=jyT2k;bs-xpfA)zSp5!X8D&v&Hxm+zO$|lGJ-Hd<6M+A7cYqg{MLvetgG~I z66swbB`P!B6F;YSR1-}5c}&*&xi1XNzv{ii3;CDG4psKNryn-hEVE2`GsX%F7|hGX z|KFEbO0)O$S#4V^N%$(gfx;WoqW&sxwO=xJYXTlhwr%h?!&F6S#4)p&MHLv9yt9g^ zlt3(!d0#ktd6D0$`jzbZV>MXB?@M#a&bd6q^5phhXsr*0SMLsQS8?6C9>bk)io7f* ztrJ-d1^;RH1VUmIM7_7|Xr((q`23{a&0v#EXa)|KbSTFfBF}&Q#HnX!h>XoX_H*O4 z-+a=^9(=mmx^E>~<$c{<pE}@=d`A1-ObD;Bygu;f0lHLy#$u0Cr25tMRc%~k)L5rX zye3upOnZUj($Z301njoTe>BqPSi7<I7qXb%IREOPC^+`2E*T@0-`!$+r^afPknpK5 z?xJo@oz9K#$u3&S_lj_|Ge=2{@*3)L`$aX}Q_|Yg1?LGZiS3>3Vl{13@MOJK)o$ad zn$EXx--3cZy(agxjcmBJ<LrF?{K3POQ)@vl#-Y$T-BmZAbHtC~UlE3RiB-D>MvFgH zCSrmTfJ?>HX1Z8_!Ki&>PgE2%!p^=LnNyThx${9f{S^dJD;7F3!jjb~yM&pcvSqaO zhIzyI!-tJ>(7JkoPMO!aL)+IUW{7G$p{<{b%b|&`>9-Ur$D!Z9dmo+AAg_QH>k&D} zVx_ou>9-)Dk$Rab*M)XxtQn#0>ar(b^y`=XQMG$g$$<y!AEVPuMcslrog+oLciB{7 za+}OYfShstb-5?*>5+`{@V@U=ouAeT8Grp&xU+Aumc#be<}a=n%T#h%;#@TzHG6Y8 zend69Duo+P02~PxqQSdHtfG^BdFC+golGi=d22k^n{abRQBlz{4J#v~%56lb`XCw5 zD1Vz&6Q2uPzt#sDj(tn?VqcQ%I3M~upF50mpFqi;U_F88a#!2W7glX^9v0M&(}4zi zM@PL@;=fmJ=M&P~!o0gIPp&8r*ZY4R*Q+%q_6<iM>Qul->txb=u2ylxL<dVfjb5?t z`v+BJSziI}c+(MoP45TZI7CE$(*S~!kDfN$U~Q2IlnMw6+IM&F5f_ao@fzBAc<>4p znx4vmwb-kw8HFrXeq27g$~WG-p+JNZO%zK~6v~w7RR4G!sdC?DG`}Urk}Lm<^oZ-y z7nV54LOPA@eEsY&SP;u<w#jtK)zI^JBNGhR8Jo48mGrEv!{wFz6PIdWZotv!B4RD4 z=ZnZk3oQ#P9pWx~H8o@Nor-m)-MQnN<2W8XT8$}J@ZrF>_nxODA1#FC<xhL-vN-X2 znRFT&{0&2IK1&3A2~@gVW0>s<Bb+Rem1bib(;TBZ7xL_^A^0z<(?*tvD;xDfv(BKS zXv+3(w?D}%mD~D@ZEirS`p7Ql&OF<;luem#s_f|yHs<Cn&!R_0zK4e9_yOz!4?MM- zB9`c`y-02XTMJWD7erxHDyTMy+?cG-y3N7qNi_mQbrXFFuQM|%J}-3{o0{I*?q<GY z;Cq!g3mvx*nfPE)pbYK&_2!y@Udu$Sgj}p?V<VoxuVsJ#QD<zJ$2-oW3;T<?VfBwP z0t#!Oa<h`IRb_Ri?k0mX`wRY9M~kB!ci1maQN%bWIXUgy>~y~N_UR9TRu&dl@5ue{ zik)7OktCmhF*)mzV*9+O)}#VH<hF{5ovm~9Z;z}umioe1`(kp^7!o;n-7ma6Jj7?p z@k8fyo{<SiD%PnDY_S(6$54uCb_DE9H{R|Fq;VAac#~XpBTnZv2OND8T~@C~=+?t2 zsq%`?u7}EF@o~XXhN3HwtOu^l9LfcmW8I6Ny{<{en2|r6En{hRpI@wrn0r`TgZuky zOl7+|y8326g95w1qJ8!s80fz%_BK<|w4k2toJ%L&oX7Q+N=o)zJ|z}#+*Mi@w{e%9 zgUFxidY(|ddpGhc&G&k~NFt8axOdo2lxqqrsfN>L`coh<2LL;P49F64Jb!2eH3p#1 zXRG&oVw4*~gMxfWpT{R8Y?|4sL)Dyh=UMqqiI?JJkWBYJn|<o-03ZzEa5#r$?X*hi zjq?oIRZ8VT!}p`6y}t40z8^n2U|_G4D={^-$<-p#TdVn*;_o}R-T4usYLFRWB>lQy z^O0{520a74UO>PY<LWwCr{{dqTK*l>PjfC8R?`=^dCRUp(_1xO>(l`+ow9{j{^R8S zbJQ+8JgDST_Qo4w6~Wt`uo~HfBjmRXw1dQ2d5BHh*QaKa^%bGY1GQ&gs$jRBHCzrw zWH>n6lj)>F#AIndC!1r)CNgx3+$-{)z2sFxj8}hjJpJ<JOAFcszc=Y{wIRe$w5vN# zMiqf7+PRT19k0bBA<=ckY|g*BK0nJ97|>XvR1skq<B<hR0mLV=0F?yE+VqSpJn0t& za^;l}Lp~s?g~l)i&mw$T-*fkq={=v)Vud<m=&t7pNw2w;h$<+mT<%IYol5p)Qey06 z*3hs`<5zZ@4O);1)oHbSnm`_UD!22=&r}9+@xZ%$#?L6bf`V61(lstTw+FXLYpQMZ z`tBTk!K8oH`h3*;`gflxU~;1q-u*a!kGA>lr*!=1fBA-l8D8Io$H0`2@IU6Es!zYM zTz_8{sHlIt3C8c;14bZ!U-{_&|GghJ|M>{z|M44oU84{u^onIajAW~$e-y{?(SYVI zJpZI5Os!8?9;c!%YN07^kZ~DvNE(-z*wPdz!-6UITs-%2h;w`Z>DZJ}qDU^K!M!kh z;wW;AzZo+Pui~f=n&Q>;*OW%38@Tv!o%FpQcAj)Z-!u(s;d!Iv>OPO8ekhI48_%pQ zI^*LGrRCxKaD)HqZpYH!Dup-)q014vB{bhNOsGn&_4oU)xr|(G9F<$U)r<T;c%n{d zdASmIP~+VmTKGTvr1IS7SD<A3sKwe0IVzo$7~!WVws_l0aUL|a9(veDpRe{O{X5Z6 zz+O$;JYzn48`LNnJIUNPIc6l5yRlSFzbawLo!C3dq&!VPd|D#N`1R;6EutW@`qU|? zE}TreBYB3%@mc17uV%yuZiA>%q_g{}l#FtQtezk^dR<YM82|h~!Ze*<1#=GaQeMy% zb)Acwe+Z(*##cmnDv4_r#2s|zAhnt1TU1W?S5PU#_z^UCf_S1JRm^wT4!AEhBdCMI zdvKLe@F&YWAQ7CYzCEYtzU176Eq8C5^ND}IrzrK{Wq!UK&k5`M_|$<QVa5AD3HC*Z zC~`Bl-pdD#kd53G9nzx}pLo#9pH1Mh5*gx0E8vD>;FpQSQhtO)p@T^TE#4(RyzAje zXWR*$?hy8iD$~gNAzc}uhZ-ik{G>8|()YbK%goZ7=DTNaiTYsC%L~f*MHo;7NT_u` zZoEgF#8^4%d#biyZyM7k6?JEaguiDwsX>->f2^V^u$(GdW&biO(5{WUN8o|ZO%wIX zoQ{k3on8l2_)cDgX|kLM%yM{{hZLmoNinrE)IYJcC%cq9>+wD`y?Zcq4DQ}NFmB)~ zHK&rBQ9$u9nq6-E>eJ?j|3u&e%M-fM$sYk-t!f*n8sx?RE~+M-0IKG`lilAtaHuRi z<Z(U1d1SO$R*L6=!uV~DC|vrl;S3@w`;y|*=^n_kH&srsTFtv^#58r#-<YYAzeTDn zwK@bT+PL>iBIP7R=}wslmto^}1(L4V1U5{$9VG0C5zg4%o<zclVAf1-a}2$7Mwr)( z!w%h*cTcArN1WDb`O@Zr#Mp!0O)p)KI4@*|1a&OoZp=lp%_Ppw<p^sA2yh9=RyT%^ zD+K>t#*edhaYVjXZBdG0M9yD~gX+pkIw~7(UmyR=1>9&l;r$FRZ~L0$?U%ED{lw^d zv_PmyjL&*QtA@QoqZ}KSZjq$WReQSrQxYCY&k#K((Z5|*xaxlPR`7)I_|$i~nn8I6 zgr6WVBZ0#5?X2uL@j^EeB6{zX?nK+Qqbd#K!>ib_##04BV}=`R4n*XSAI&wznl%Z2 z{_taMT4OiDTzm`DG$bbDpOI0NpeHe;UrAP!%2h(jhmBuUOoy`cBH8zUeNN8SnEVuM zjxreAz3ZP6_rTU@b80YN_eQ)hujjEr$DXgam>3&1r-#d4bPTo}lQt32pDZ*=$LKtB z?36Ri*V)(=usbcD6kpt%Xv4x!XmU}x$@D%JcOa6ZXrSCmg`m<SX>6P*d<M+|hw+hR zQ(mWC?Uu4!%{nP0-!YHtV!7A&;U|L6fuThz0|U+d8H$HtD(v{d2*ITDfux70jMp-~ zyvnMF08K;?X2UvC)KL{OCZh}?juFsy-EhAQi(9%64DdP}2eMnh``x{yJv<W2a-td- zk+(-P%^8Mt_3X^$cEwYr1O8%tqoYdaG)UUXj2@G#%1^$0UgP&YW-C=qbM2J&zbmh6 zRU*Z=8!U(3bPQoV!a~k@r-yw+Pwa$eh-;rN_*&0N(Y2{}T$7%Uu>)gdiv&t}@9BQ8 z^SbuUd>vETJmu|?Z#B|lc_ElH&pudAA}l3W5i6-~T`zi+W_V^s6X0RW)HD-^cLGxx zh=$c3rt_{%NAq%84#JT^drP?AyL7PI#%cT?ITL;ZB`M_XdYb<R>UmgT>b`(HccG>W z4BC_}z5kexg<4MSxeI3umS>Xzvf6<IJiVDQDiI;m-9yTA{C-gGDWtWP%j(AUs`iC3 z0|TxEa^4pRs%*`hM_LA3=XOT%o_PQ<^?aju($Bazz~r&KoE!@aONk!1S77gfYvb<M zuQ1I9JI;=ufdAluIL!g3yA=V7ovNy;)jnbtk5QakQ;tG)>xs-qjg7a>dy5plH5kV$ zICjKbCgaSH%Vu3kG|J7YKV;i)^;2HF)*AoRG>@itzn;TcUQ#m0n3o4G@XPx@ri%?6 z374KYVdb@oMzpVCY|O&|OPH#~Gc0c;^03r>EO5qv#dw5n%4RrN&7`||0j$m0=s1-$ z+0xkwpBYJgS50e~@FN}<`#?%wzQ)gz#Dd1sJauR{(zmyrMu=y6HZdX5W8s*T_W&yF zJH%U6C?PIJ$3V-&qiR2IRw8@5lkOKZ7`A9jE|=E$0RpfQahdO&xU7O2Z$84418t7) z-)jLv#N{O%8y%jWKPn@gSr<!f#4Mw4t=edSt0I-reb8+k57-rRbU?iwh+KeDb&x`J zeecUzJ!ZklMI3x=GFsxF8BTx3dU&N54z7BUC{`DDM1MD=sT$P%l{9+)&?mMkgh-(O zb`k7Z!xTos5xu~uRUYKScysLf_zU0%e?dQ2nuM2|V{w$|Gzu~@+T6A*dtK<>t_MN6 zhHNh{hkzF8bJoxP#*_m&(rYO^W#M`s-#_zkaa6yUTNJ(6+Sq7sZztuam$sJ(Ps>b8 znVSW?ZwAf!#8F6Hoo4#&{AFs~CWRTUSQFh8zeY=KjV<0)|78Ng`t)Q^*HL;F`>KDt z+t0c$7MMtPG)#eW<nOn3&;i*t02`|sZ^PR3wED`zcBUaPupj__9_wPQwN?RLF_BAJ z@!Z=wVelPgM&bv}`QDVVpJw8ky1#hYo`*;?k?~nset~TVOO#$sri9VKO^u9n076rE z$1+>4Ty{-#NrBbi-xZ%%bG|tYw6U7x!B2?X3+X7or*luv?<NOa8H%%A8ftm`Y^psr z<t6#V`C0w(IL$HxeLtX9-DeL;5h63(G&GxH`pj4mL}jQDNnBz3QEaQVi_1wCmArX; zA>@juO;jQ*uO>Q^jNfbJayUG&K!nfA4n(+tsNq{xJvUVh%Sj{QJ<Y14Rk!6{&I`c6 zxRBa5G%y&2$-5FxH#w4_*BMd`uF>aFU3*>qck*&m+~)Yq*abNrme#R1qI|wH>O~gA zVcz<Z_625uh@qij#|bcP3XqMxjOS4@hRS|j@CtnPh+)IK@(`hW&{OY<6c)0!E<Ue$ z@q(=B=mOA|SHe?+$j!XeRJ~x6Q1j-#OFu8d3&a!<;uDq@{Br4usLlV=Ec(-OL*qLM zX?u3CQ5-5)`NxlAsxncj9YGle9*--@u~26bAh>CGalABIW_ZtzmqK>h#l=O1={2`~ zZ*kh%6*(E@^9EftV9>>4qBw&)OXG@jWTH?LtQ{8?lvUiT)l1Nq79^vYc#!Mm4^FzZ zv;66CIXGWBwdmUDb#vwzCEUz#>FhUJA4<sM)(3+Q>DHF}jJeQq5D^mo8e#GElGP^t zL^HOQnyXV+PP?AMYCYJ}Mz!&|iA<<a>`<J}7vu{tj1(99-?Y(p?~-3!)Cb|vjUNal z8Vqs`Pu<#b)Xo|HQH$x!<K+gXXwgJf3k!YULW_n1#|=Wc3RBBo(xRfquNwWNKojCZ zhMb#pCkk<$3wpU8z6B<AsU$8x5xkE!G#yH8<x(oHUq@!;<miGejEA5LBP?agc_Cwy zlZ#y*^H5MhrXxpOA^m!`PB>9b61&Tx_Zc-mzev+`d;3ZcBK#ww&*njs-F|PHucph| z-0B=^ZIdO*lt<N`$9x)_@1sVNX-M9O?DuG@MJh-2>yFElx`nk$HbSn8v_jq-aZ)la z$Hg5*>X6T%-a)DlA3oH&&(fX7t~tND7&9&Rty6JN<?<x7UOUnhq6?cCiLf@5+JZ_u z0(y5N@5*5Q%9WrmZZtExRt4PsEE(nown@<f?23BHe0oiZv*lg(@ytU7<LA9WjVCc7 zC|6<+16n~oHz0%`f|~P&-*yeKhy{%B(gbLRpO1`H@(`L(p&jXTh4I?acYr@Jw^Fl0 zPui0&=_n*531B7M-tPD!v^`%jR?LG*b96$+nhV<7eeje_UB~459kMU3Vst7BPvn}k z&jvi#J)pWutN0@Ro*j^#ib}bi`)r%c;XS%=EdkKp5pr&u=`L&y>e^aZA*zjoP0F<` z>Lwu4`)AD=%VlNWBA~|H9u2>V64m*=h}#wls)2r)v<LSO+%-xvE6~9GVY&~@qGV-4 zT(6-mGc(`eK+b~Qd`41U-E|B^cQ~Va?(4KGMib`jR7xh`@GIBXr5{v#)T<hvzwz=W zcHHrnE9Q^*G*2m4Sos)EdG_k1F)vRk$v{x?A5A|<OQJWHIf^JkWNCL{9B)%s1N2|V z{ofm{wYhw8UH}QY<M#IKz0S~N_V;QHRW%iju9sKosEyP-phlnV@3)6djZm;&S-iMn z%i&oD36RRkHDC%@8KL;9R=DvNWU@z^0t);xI=X@P;>GU-LBOGDp-vjAH&l7th^I!- z1|T&<Y^-e9ovzI+2tbWYOcqWW);IJec<c}*16R|x1IheOcA&(*v2CC2SXmmc@_X8K zN9z%77_2kO5x1EgC&&p|dwOf<^8+=5vUs)2H6(O=6ymC0gW+ucGc1pbBR4l^wE}t& z=cy=#15B3f@6q`bXy?7NW?i*7Jpl_arv2FaGlGtCMq95Et5#z~*4N?h3|1nBXsjqH zvuS+fB`Y4OQ%W^Fk8+MFS<UW*?b@*odH;MNajI8=fS1`HOP>U!ThDpwHo7=2UTL!B z$^Pce0j`b^&|Amr;67XeClA>-P%b^GSKO|{hION9nK<&A+R)ZUS2Y#^bPqcQa~)n+ z9)*+p`Z4(@d_@UXtZD|Kq+F}U-E>kg@(8o`;hVeQEd8v&q{YX&%ZFQcc+-5TaIM&l zHWyVxKI#A*O>RT8wu)Rn?MBSaSpkAU&%kgOz6zJI7i6eNw9E4KMY0_x^?yjZUWmY= z>2#LjsDGqp?}#{Ly$gG&A9dDB&GJ=5e66E<Ur1&4ameK;R%^3j>B+y;g<${WH(la4 z-lT0M=#EpMQTZA4#(^CTWxfj<uvu@A(7$vBHMOa-ha{|^Pm28CM+%t#cV;-E!%*l~ z-zF&8^HTLhO0|bb0=4U8VQM@xI}z&w;V71{yU6GhA>AgbM(=m!oCHaw0{SB<1QBIy zP=`!1&4AR$<WJ@sY>Kbi_5X+kN|mYu8ndeRSk9~ON(HqjV^SI>_xhyP*s-!jtpEKE z$6I|$oE%W>i=oGnKC9UV<|h0&d2DxouOLLpisHD$Ztjt+LVLZzV;&pAEf?8&_oeVa zbOTDiWW`H72O$aZ2CFxBAE(Pn>7c|)u?ZHOTrQ9sVJbG6;@^D}Z9%#7PNUpYP`VsX zJNs2nk85@A0}tkcx2H$<23R`<)G?iKN7Hx9<$y30|cC_JMqyhk7Nfh{T;u0%>K zSNn^uZ?N06x;Xz6S5cBlpnw}B20w?EjX--)m1qa<_zi>f?81CQtE0W0*_l<pJHCKz z?e11*guoCUx#5z=BlSqy=JIu*AEP*d$#z@CnF|S{fz*|gUu9LF5D;pZ$MZCCiO@zc zC};%YHJ_wh`kp(l1~=<dP>9xCI8(_J&(<5Woy2TxZdTjOsECSwzS|bu??p9UC36P} z?1eQ^y6$QQG1-qxRf-GyrFqnTn1&2_98z0IlSPzOb#z}R$Lf`!o0R)YO3SAmj*VlQ zKDD~AjFFy@+SR=PN@8)|lwnU#Oyzdin3?tOmQE}a>WO3eV0$9z+5LdP3hW}IlzZ1K z^4B>_cGI8peJ{SrSPca34W#%u;>YVYi-iJ~8PJ@GT-I}qMVAm*e^mw8ZWRumi*nu- zw7WVT^Ef8b8!V|e#GASuxTfU0L`t-Gb;(~W=JiexB}O~%PFLNE!n(XJA$>PyGbNX4 z+qd=w6Yb?96B!g;694^okmU~wblT=<A1^crtsijR;eCCJ+o7C(m>|Hzb3Xe>xo@^_ z(g=tX5g{o@^X;2;Q|IFehiwwFr9~QD@C*#B?xEtfIDX@;9Uv-8h=cIEoz)yDo?GRH zeGGur>}FzRYXX6641km6+5)Mxshj|mp8H(wfP;;Q$+nK?d?3StGd_>s%)eZ~!zYh( z8S3|zlOiKU#<(o!19Dw0;U5uxSu!y|E<4x!NM^^Z<b7M`o$01F67l;5U7RPe>I1`T zg;t#J(<MmxpsaDv&`G%L^c8Oi`*2f?^LbeMV)gZ!w4EAD2n=~4iwq{^>oLv_RKvUT zH}D4t3yGo5mwGiACGw<h#UQ9v*DjiLY{}ZJ=d-hSEHvhqu|jC1h|S3=!>^3jt*v^8 zS-b9+O+F+(2OA}hRHx_-5A{mTUU2|<kP?Bf?HyeRsQ1Uc!BERvxJ(<ZkmBl&QSUfU zjldqsTt&y&a)|J2ZqJ+Y-Wfwf9FOA-hugu9<*zPg<`&1VxTSoekbJ4XJ*>)UY13zo zk39wSyKA4>ywj|8JC9dv(?h?INo2N|+!4cdQioivcHqdz(Z;}?w=VgAqIcPgVw~{U zZNz)U>Uut%Dqd8As)hFV%UD~dIQ!D)YyvrMKIgRtM}lbe%hsGC-x9>tER$naE2-x^ zFCyJX;4V!O5YGDRl)_Vm+O*7}tqD8PPkLN89XPqAtrQVTyEgJCb0)lINy6DLD!jjm zpKF<Xu67`V_i%9zYeY5qt|lH+NcD!3UQRh!OQLCMX<7P!Mu!16#|Ud{D&eHfm3<f> z`31Z}?c**GP%GQ-<#-116f_On!~1T;=e3icVLGTcS8=-$9tDd5$A2FINQ}bMyf=RO z5OG@P_yxy71onN`Jokc9z*vpf*89nvBpyXq|HV`&pSuE-+nz2~4wkR`zDnZ{0FWY; zy)zG<c|kP{4VQ&(&s2xn7j8EzXk+LUgB7T+9FHLw7#Mi8rFbv~JIk$`_FEbusa2CE z-LLPx3*=HOJad4Ag>L1~JWg=p*ess3ub}hV5&$D%mTtRe17iS*f>5KGKw;uQp1|!_ zv)~o)6}ureEEz!6x!>?`uFTRG%E^GI&b@O?#HOqaXM8-$VX2b!o9Yc1PG@5H|G0b0 zu&A~-ZWux2hyqF(2&i-`-Jl34-QC?I9YZMs2N00%k{nW68l}6tyBT_5n1N@Z=l^_u zKR(wxpSUh&@0r<a@3q$btDB$Oup{GGB|+J0!-0an?mRq2By4ZT2GS=LO^A55h<aR+ zP`o$EiCp*;aeXpWVb4WYA3Dr-`F3~DhBcY*$6-m#{+_lha>)nb<*6#CCjBCO45rH> zDEN|v<#g&e9k_DITX3J&s-boOVgBnE+eMd+?L0cO^c4@3=%F|k8>asBq{gvj6ena_ zLsC+5jV?b~=-iZkZD6``zWBbXFRV4+;cReFeWfW)t<ua`i-}x}42eLquab!516~j% zW!#-W%nSRSJYaSN*%^nm5!}WO1c_fY)jnkU45(`B5tO@c;XD1z+IvL<O#-GH&d|BK z>l4l9#aj*MT~+Yeg<3Ir<$~Y}VUg=Hokn&C@|o(ef4@3_Cw>33KA3~PMMneI5O}3O z_~>4t+?bvg8F`|RQZ1!|t?^t%_x2S5-6-c)wXh4Ex80uBYr^q2!7N?E$`>Uy1=H^6 z3}UI0hhjKRdwgzd+ov<tg-o4a6B0Cu$w8_6`!hv;vhVc-6`cr{CN(i*)jj{u;fW2; zEtc_hk%;}NwF4klKM;S3Cqz`&D>D`+q5Qeee5xYB^DXd<DuMF*W0SDZ)m*9T_(>2T z`D~}^U+d$}*IX|CZtJN=H^i#t*l709NMG~1ysi0o!w`F2{$4W91wl3&P&TcyWn}is zXy>1ww%GHU<8?Bp)CKdXjs01EC{9^<n~3Z)6XpKM7*3i3Ow>|k_I(@v1Im$GK0dF| zgbawFB({k6N8Jg+jowFw@JnxCSPcjWP-PO3l#p0BJ~PB#-!zbJ8`#{mxY`>rABc)l z#ucthvBj2}LwKQ9nNZCL#r>||EenFsE2Hzls~ARpYb&b@A&<9Q+82}N16V{vL>Egq zjozHW<H2hU2~ziRA21Q)Ho%)g(5~<cUID&?7OczD^xtfIQ4Kg@XFD@~kb38ta`zDl zyg3~P(5z%3&|4<PVXd%{jK|>uB62xjCj;H8+W6np@^u)V=z|~F7?#YQBmY6BsqhQ7 z!LLW&M=a`#*!`Cm)za$4`ZepN-Xm2MUI8gWKJJ4)xx0)t8+&!0W3_-6JbqMyYBWPn zDz@a6f^M*T(-n`o?7ID>)j8OQsb0QRN0%<GvkZ6&m#T*3M3Z!Q^Kh}Uh8b;VQC{U{ zXSd<X$jd_}7rl=ku{L;hFwxSQjqmycFw0Ti%+k`(R(HRbZz?p0Lg?)J>~ia^e;J4P zCY-kKKAzX<Ap9_#W4+R5zBSuw&68U>sH&>}3q(>W;;goVWjYvi;GWy~v~X@ubzZ7+ zWII>3MKH3_-rkPbo)3Z)>dm!{><G((w(F-eM_hFB`kf7vVv=%p7p|^S!Mi)Y(&~a` z<>hOo&;HyLxIr2(b56+4kGQh+FLBihO&CAiiYJ2ArpFPz7jfb+pSq2i5&ZBax!S9T z4;6(j@gY0&=763(Ia{1B+TWYWOLmyu%uuQ3WKwsZ{bC6ggaj_HmkW)MiS;n@X%w$l zswsBdzE^;5ql2!#K3keRanQRCeA|KE-u#I?UC<i#60u{cdbZMNq*>`{h&914bQ$`? zB@Zm$b6pLg85u{Jehm8*v&RD&Ep%2jRlDQR&0S4pC+D8dj`1SpOpnU^u^n#6RIwyq zL76~R@#Uf!qh^_f3p`cV;%NutDH|yN_E&`^aiABI!VPtE_0HAOxh_ZBhno?8b<SIh z<D+xgYRXb`969+Kg^i8-a3320MGp^FJ{7hC-4#O$)L^}TE>rp|a=yJcTdu=-^E5nY zLRKy=Ka!l|Ur$q**7#z5d?SXZT1VqMF4~k>sk9jTm29NWW>ej~OC@TynIW1bUU<`2 z@+DW_5L(zTb8LS#=Jn6vjKg$SHrzQhUsHix^kuonBEf`ax6Cg9qi#N+U+cO5eqXW8 zh2zf#G5y=S4%+uwg!hzJFy+z&#$hr(!E%D8wtHn7hK!0lzl@SqvN~GrgJo;EpzlYB z*gxC7KmI8~O7~!!=Q5R6nIa=&K#Hpbil7v7ABTzU0CIedX+m7Q<78*<Z_$n6Dab)2 zrgjulh|wFbkVIrfPx=XTBSM=IZMF&H#`ySS3tiWI86IOPKSDG~gr)%Xgi@~BDMtsQ zGtKh$-wPBVHO?@|XPm9cEQHLYIE&g5<IAz7U28mGsr>T>3}1CY`A@8x;cp@%u+?R$ z&iFV(zy0S+AC>(efXyGMlGjr4xaX@mr6x}7n0MvZY)sp~djt%toK;od1;Xu*(`H|Z zyjz($ORJs?|22#GdN4+i7^LoUnvvg&P3bO+)VK=AT)&;1)2N~$(arn66ToicEIxZi zw8gIYL@ttnB-b&rFgftWp`k|e?YDC2U0<}6KGA;v?~I9{N{a9JfUPBy9&`D5QNBr) z^Y2fHX3eJhk3Prn#Q1-nAb-nnw^@@gHnp#;RfgG=5LPJO{Q5-)=NB36R?h$B){g1~ z5j0y)F*At8LSq!}_nR?`^+s}-e$dbl*-D@OkLGNb`beJ@2~nc$iS=}o3hp@EV%BU1 zJZJ2;|J9X@FghG%%Q$x1H^U{*WM#?+zy7^q=>bwE7&#Bf;@Yz)MKa$pCO!O*t`^0M zsmGA<pOu$x5=@LRyUrW_yTQhY#-w4x=*ZoO^ByUm%6vZ+a_8t}pC)Jle)GzUA#@=s ztgx9P3RmgwG-I;+xFfV6eCI=F=^@@^bi0zQs%yh{|H1Dc^)3D$aH<F@HYM0@kfeKp zckHoS*VWc9&!)IxYh?nLE$k%kfF5fPt>}{|`K+sN`f=!?GF{iRCsnArI*$}H%H-be zs%ZcPPj#G|?PkGg<;?vJ`fB@%L#NB$p1pkaUTlJqdDl6@>*b%%ipHmb-AR0*c37`J zU!<_V%d!4i%}Q<ER$740vahW0@Q$5=?7CHv5vdk2i$a}&!1LFy?Jx43a|iV9yFFAH znkxN}c}KUd0y1Cm8Vh@^M{}%5H_9s#fOEW)GpxGo*T!bZLiygFd=c(lfsd=>l_P)9 zmPKRK*+y~mw~s6=cyu#IbJILkm8{aCpaYEneyR_}2_pwnT`siTrEjOSWNA~AAdJY# zcH&=HH6f|*u>;q?s!TnrTXE*Fj(n{j*1wVqDz|5oH5V~veGcMz9!7w47DtDQk--N- z-baFrjIqBi0n7M~P3=qY715uOKVq-f$C2iU8m&07uJ&?$#(&)uBt}iRl^=SULSFbt zjuMtzKtvYTx>x{~6^&#>Q4*yjy8so*o?K2Z8~Z>Ft{dCt?rtGilDJE*y$+h^JwuH4 zco-NguYQ|?=)CV=WMY;e9r*CUVlI*Gl2Gl5;+bdUL^F-HA*Ryn+FDwjRd1Q~8eB&D z=Pvbc-vPQtdt~?mVsktqE!8SK>FSDykv~V1sG2sEtXt%}AqN7$fbt2vv43dAId;=4 zlre1B11aLgZGKe(3Z|eoLV&iZv7)C22fs}#D=Fm+XnkBgx3sn0OTG3|vrDNXsDLR5 zzhvJYPl59DPopoL^k2CKyr-_UKb8xrnhU2uAvW~Y1U*nEtr6rvBld_@&;8&a2xuLh zttR3{pjtpr$Gq`n)yH+W0eb9tmGtB>F{-I11TuH{-IBk;_OxZyz6rE#*gWJwQKYAN zBT7{BHw%B-Jz~|(mOb`C&SqO>Q__aURWo@TIXE0uBpipQ2=+kCY;@|YVo96gmRD9b z(Df&~7d;V*Lw2V-S5<Cyr&g*k^udsFMT=0wJ{>hRST~Oc=UE=H>Zb`?3x|Tm?t9wK zI5U}$!>d2#0zHOQ(@FW)uqp-WRo<z#`0&&_Ip${gyF(K6-*i2#?2vjE52af{o-Oo( z@g=u#*W`N$vKouMyezNnTKr$%m3-AQ_*Cf=7CL@z?}bCA*Y-h&ce`ZgtehYVts9EX zTRpDSgm=BhefNvH6O)ua@d&cVmbprp#a71fT?aA1EN2K(!@q`QcF}vsm2+kiG<c3| zG%l29MRRIRqbLKPZ0~eUn2!__4Pc5nS5!RP>&Dxh*HRV}V~nBTbwB;J@<`&2#~QP2 z3zf0=vzc&V6L_ig3&RK%vqJSurQC75!d3bY6Br@KjV1_7;sT4D@1Oo51$wCm7$4rf zQJI^%(E+6W5EXwJ5dec77R{tiO)=WB%$39cVS}W}`V`&2`}2CPnrda4@ZxOa)+fpE zltkgv%c*kjpC0IOKwqDqiA$?d2zx9~=&ui+l$*;Ya6y+~AV9Ioc;Z&D-iUYd0cvQk zJ#<zLbwzjiSW$5Zz!MY2s>j1qVm!skuh=<vhaF#`r3Ui=`a~}1-~bPktu9IN22~W_ zs}g^kxyFg2PdiaK6gv~FGo_c!K=8YkX>lGN6SDF$b)rDo8kFjwkko8AihCq4sGF~1 zH35SI@?I*W+nU2th^%_Eut0nLq;#%PPd$;__AzDJPs6!jJ6(Q--vd&2bK{hdc(~qE zl^YKxYAZ3(@cxUfjwd4MLWi|nnG~_5YoJR@r7*vZ`DxkgjN(An2;}#%_}md}94WHy z0OgFoe?v~QpvB}*koPsLCg|h2k@V1SYmwbaVF6H|69w8K`b1fv%{d0fIeo~)?qdPt zw#pgdh49@E5UTGhvS073*-awQyJMgQOgeKu?_mqwo1xOXBaM4aCgYn!Sf+H`6aubM z$OFgR`zb>1?{(e|V-rBEW`7>P({zmDiwa~-I<{@8RV9jyyq@=R5Uo~IF)ZFxSW26N zl+1;M%0Ck+oB<iD7U!vO4y60)E*|bM=%+$9wU83YQmFvsyI1S0ieBroPpUpn|AX5e zyfcy^jy5fCN<plA%usMcM$%QfA<sol2$F>?4+xBrUjif#qsaMIA|hko>~NSm!9kPb zb7A*iQendix8(Xv3_lc>)dVZX^lHAZ-@P-WmCvoBw3uXot~9&&c~_a>nXqC(fOK}S zTub0tNtEN(*4PeDu}<C6^+8UipHPGCL{7G6Y+>Op6;73<_0Aw5ZQ8-U(E>zW8BcYK z45Z9X<QaN-%yc!(5ncQCUGHvAX6ylZ_Q}qOSL@)d(W{m$`;C5mTqrt#WD+TRJzlJd z&V3SfUJX=T1~ywx12ef+F;YH|xk`^UqsI4_6N%X*&#c!fzX5(l=5k5ksl`lf+bX=H z2)V+Bn~ch<xlYnrsKoDq1(wYGa31=c2BevC5OroWz~@4Cc<eH7T{s*~^#X+{*^{RL z)k?PN;S9#4yZpv7<D#t*U!-%!;YBpmXj@<?hGf&o(n05N*4cK<3kh)7aDCHNE;SiS zrs|ld_@`Ls_yF*7>*sn`L_m`4vA)|TbQylOJ;VT##l*`40{Ff;-z+|h$LJIE;>p+h z1DEm>C@+g~gkRq0r%y@wOl9D>Ys_{YLi0aXc-JH@T1mPS0vm#aowgGj^%5jbb@JAR z$_;`GnYee2%UJESsx*dM$JSKcM!&*MUcY(1nAa(@G(O+nUHp0XYug#iHo1ro6%|KR z0F%I!g|+1nhWP#u9%3~noZAgg$8&{ZzQeLzxHKa^NGrFrYWN@`?nwk7o%8v&wi;a@ zGN^t<JZm^-6y&F2nH!z-)v~x7x~;bIXr@eY59Ia^VwhUyzfwN!6qS#|tnm?jAIcb3 z_=O&i02A~lVS+S2oyz;SJk&Nz4LM5+vC}~h)UG%JvRS~z0+fhdT;T%5vSgfFqRw+b z&?@9bs?k_alI9(w;^eY8!e;*ibjOVL(kFa;!cIk%>7Ha4tgIwq%12_HFVRh=U2Sy* z4{IoF#?%8XXZHzxmWyXYn!nx1gdw^~YO)ukLP8Devd8rY4QioQM@#&b1^$7OS=re~ z5Ew?!4f>*B=LEUt-d4`HnaJTQs$X_En(M^~p7JDRd9J0^vmVNTK7!0t*~JQA;56OO z?km*Y*+VL0|1`sU<bD2sA*0|C?YQc)@=D9`3d5Dy6ewcT1_GJrYN#v<#rv}uoeL5_ zO5)CIJU(#mj%t^<vz{X%=TcU8R67QfIgIouLQM^|j!qtISW;nr{_RPD&8%7H_-phb zr;#W^4Rs<IJi^)F3q^cX$K^)d9h2dH9?2#<|31agUhYtV(Nv{pjZB;mi}UqZsD41h zp$|o^!^l-vrbisxW!1O$V_QX+{a?vvv(+V!1FwF?K|Gpt3l-9Tez_)Px@NZOHKURI zz>lq!XJv|IG>J>VU58Bsdn)a}pxHMCnV7t%T3txEdhwN-gx(i$rpCixhUmigb55j5 z<VrPM)LAb?e(H=g^|*0{a&9C@qgn~-S&z*pk4kX#)X%%V;A-b_)-6Ytm#fa=ea+cg zs195<+8S$uAZDeBf>eZ_@DTub3FX^=X)amcB&xU{W9SZ9r+aW$#2$SSlYv<_oT_<z zS;DzJI;4AVqi=Y8d>$Z#mD7DC?z4YszqBvQc5-v7u>_FWQx1;TSxBo;#xvN~)<Zs? z!Bcl)3tQ9T$!d97=|{L<VxHN0xY)!dN5;lSD;1TYBRD85x5{#xXlZl5)Q6?>>NXZm z9UeoV`CCsC`p?!rzinY?9iBO!sL!h^%!a`nVv@-VDFtIw(wHOOQ-~0lm5r!?)+^<< zJ#n@RzsG6|FM8DBcJU{)jN`w1C+BmL0m2klBC@b)0+AKB^qxd~zz|XBD*f$mjj-Ub z#1rzOP`79NV44-Fa!V~i{kpLuVT;{k4p)`@UC~KG5dMrqxb~=8%|Fbjc#Oi%_Vz@^ z<b;GL0C@tzdpLKn1tw=mOeFG2b~ZOb0%4_9Ot(~Ka>_FbVNYhC%VtD|=Oj@5v+mE0 zVS0z7$atJo6B`_|gLk-5QK_l4Yy!u-HcW%?NiRwfsDO5?BZQF+j+4S)VVHk@j_e9- zfM05AB_3A~DVadb#q_D%7koB%PtF7b>kyt#x%1Q)`<$jLH$+`rs+4SUh#C7<YI;>f zM4Z=u*O43__6UUzn+-$Gu@)q4wYn&>bSldnO-xRNAZfME(F5r5;o)_NP1VP%!f|qs zR6*xP%_xcKX)RRc$e-c^n=V4?ddd;0+V5Z19^gTRtQqmm322XhlL8tDyeXYf(1*`t z=#rSbZ&Fs6!WpF6NORg#w0JnVIX8!MmLke|VvA>FQ)aTFN0+<=22C<?T!cR!ZGTps zAta32nJ6A^vMyNinsX(OB#Cl%s*pHy9k7n6t%lU27cUlgC?EX8o02>dPZXuR!8&7P zlVvV_I5FB;X(oQrs>(F2R|`5l!mpn`-Bj|Bmx@in)oM~(`}X5s)uc1)f4|&8)_T;I z6%I9sMKyG=vS^{%Y#mx^$MWNnkJB`}aGRfEPWgpIS2!7TpfuCwF8VC;SdwJgFz=~J z^>)6e<TE=0WR;VnTts+qux^!+Eui0>ow@<e{1c#rR1)$!WglJop0e`Txwp5r5^PRT zm53{DUs|@W>c<svb8+ntm$JmDhCdTXN{tZqe18oIAV@uLDFj)ju6wiIm;GpfpV22d zPFT>^aaPYa37i2lKypOH5Lk$@{PfoGn^z<VvM~frT3uCAfH*Jzdc?dAL6HiW2Y~i3 z+^6LASsG@HNumGHpIS}L%`IFqR`(ZO9HPQ;_n5o(>O+=h2CSt9V4eYhk5!|hFgeDQ zE3O;t;f~!GtG#;CuxES0J5V)OaC@ShE8Yw3>E@N(eh^g2<K&p_T({$#7MUxWuG38^ zTp?04_MN7{Ozv|2vJno{HEViIU=}y@yBsnb*5VVGV$&_<M)l1=%AfLB4HnCWD6Zr% zUpjk_wBs$W5V`IN%{Et7MMV56CEB<{uxV<mD|~L+37ka8lgO0vy;X}xD0a>lFJ8Ew z<J{bGogs~OPug~%YstHXs#Q^UDhmdsOa1dpy!fjX62C@ngC+=dwLfA*h#H8sL0^ZL zlr9j>G|EAiOb)yE>e<=o8aH-jacW{&iZN|igo9gLRcJ*eXB**RnBT^QW4UuhCH<%J zNEkJ?f$c--E+spg4d7sp@Oql^*|s4dfOc&d>?)KMU$ZmTdQ>uET_RFB4T(iuuJ`)@ z+InfWy9(4qz(LW(c1?4g-4~B+0EU+Kz8PA%1l9ihLRtT#g|FY{yo01bne>|D8gV@O z#F3nQ*Gwkmr|l($cPzXk-1Br>bxcc^h$xEw4q`KBS)a^wWhXAI@6z28eznlr-Zqw} z7qgrl5MXl;^sYCU?!DOKKOGwy-=8hjEunB1=8aRTfQyNgJH)_Rg-i_6G)Gk7Q#2uF zaSY0404YTof9p?~jk{#rtmhs9jVO+4#2g=?#v2`*4i5eWqQ|=G9ttl`qe)*coG>d; zq1RCOy&F3PYRoIh{xW&pnWA8-h&eLdaK)iyEPw4D*q)D+FZ>HqjUgTP;j8oQgM+@5 zl>mipYeHYGoVWu4bAd{+g`L@Sj;b<}e`R=W$8nv_uq<l8pDN&N1MW9gYg#_dRWKxH z;C7$zW_<G+u$ijw`m4NMXB3#>glPE1eAKbBy2B*1;m5_V-tEZjgIPZeD*>oy*AHg? z7}GVV%Qik`aej?T_v>qVJB2=!ou~MzNjdxdReo2>_m*b>?3~Kgs_{U>I5yalg*zL$ zVJccZm1|!PGau;xK@%E-XG`Twk;+cX4A$0hZMe#J>+V%Fl7`6kKt7*4jXUSsH1*r{ z3;(1)|NQdh%Yl?8m0UIFvlur(^=x*P%aLn!w9zr^*MxO%p*>tpZ+-*@#x;ZCEe&b< zg4uQD+o_ARdm4%^#~Vj?E?5Fdzn|J82m=T{=1-^VCasIeAY;8~YHLl^uC6BXN~2_D zW$6Rc_!>K!oB#99pmM*YV9J)!lo4f!Y`_{%63jr_CpYMjb@LL|KNQ5SUCkE7EpF%E zjT#cpor?Is=w{IU&suM%GQF@VuZi^&{mfds2L2vpm6xUVmZ3G#+vJ6MwBwAq?BD7L z1EW&qx83LLe?bz<li1k4NcMQ%1?$iL$-Aio{4{Rbe^IwZ$Cz`4_ocCV|6zP()XZIX zTQKEfp|JS^n#j51|ME3Qbr@j5O08pqf0up?4EJA_zy5yy|92lx#wNvsR{OsK9}H1N zMTxfW1)zx)<?GkOe?=0${O5Uj=Lx}W41-5MnHG+|&tm`mj04jyWxdjTJ5nUWAZAz) zO?RkNP@MPCU#t~_UY2+zRq$j@u8o)s+n+M#o)CsLu4aHnO$2um_u~z|zoMT!65+e+ ztUfvBJ->|&nNBbYX`;<cn0SuJn6L3M4F5C0R4CnV@FeUd$6)xM+u=tet#q$~oyx3% zH^#=_*d7qC>N%_2>sy0mV3flwNLux_>27RL)wyh0=lJF=HT#19VF7#EZL=l3g}1Q{ z|NIPJB{99*sxnQ%Ef2eTwb+tK%HEb>ByGMo9+{uRk$%0C&+Pg4tBS(1R4!u(lnuVh zW^Xev*#J|>_=6v1wUKd$m^e}4@%PN%{e5G!4yEeu{V;PikI3PVk1UMLpL;*-DWhSO zV7Q~J@}Dw9>PcIAZrN}GjUn;lJ-oKU0}^qJ@gW0JQ|kGW1?yEf7Hx^|G1gD=zxcH1 z)^mB!BLT`D<Mfv$>b+9xg}R&ObUno47_@Tj%cC+|QF*Q`Qu3l|^;Ca@%`iZ6^+2O3 zZni<&Ac8m8G$=bvf>4bbcyvN;7gm{`72G~!ztYY0bWPv>&(ss#buTS=D{gry>7Awu zF{$D!mV6AEyzja_f+4}p@|mU(k`WOKzrU1#g4OC@2axpCph)5{BsS+8$&)|KNyb;^ zEkBD;_Cy^n=^Ppr^zHxdOnR?#3}DPbz0@$Psz|hqjg^QX6$#<TAi}LLiMoBu{&yL& zcmB(k^;I?FEL8rE#H+vPK7A0eaf;Cx$7?_Awo;xCy;JXW<m8o3=&ro|F?xGqe>A%- z6;OYA-^_d(pTRvcVRT2UtiWH9^uL3L&Nbp_3_m<6Q`dPKA{EglYV8%|7onlz^k}8A zNll*#qendF<3rfzXl7RFw`{bm-|Kw-K7XQ1gbrm-a|1;4a~Uc}V>F^Ky0iOBDF2%+ zFoH!R1a1D_niz}LV*md(+ckQFZgqr2LGRwS@QGyonI;;k<(@D4IK0ge70dh?EjmbW zTLG(W=e#95$LvSR8^#zj6O)lcIch>HE8M$#4?Tt$?VpAInY0fQu6EQu&=5{B324hC za4<0?)#_<!z0@q(u@)71AWBX=uri?jPjkg1Kx102I6HAxR93F;q3|Q&IL|I`9#-K% zqiQJzjht-LtZY-ob3i$YPu&v-hsqMStPISMFfC)q42cK{`Ejet%O|ZI&`^SdL={&A z3{(PS<>YeN3v)o3d0IJ`LHQ4c{?0iSoq_m!=2)fKiQ(bViHW6Z<u%1{mu~g^z?&yI zn!^1Qc8>qpxjj#smhdhNDlrsI!hV8vYgDR~|Ej*)NL=mKdeia9(2$DYoIhIqnYtk6 z@{TAt(wFTmOgpA-L40_G116SNRK!3`s%Ci`EsDXVA`>Pe6<mx7_L^_7?Xp9JRQCT& zr}Ur$w=|4hOjn+T1!!%U)hzNT-&20_e(uYEsntPABMxFSqHbdvCa}ZFPu-5le|;Rj zd2*ZMXyN({*m7-hkDP~ZW4?KD`sR8E$BL<#kN_>}J6!a<gHl&_@3+9!sv0ryfkl{^ z2I1XzB8E7pIBp%+&7iv=_yLexH@AJ`bN6uRRPMF8SA5%9TRS@@<{@p%Y_lYp-Uenl zg_QBo7%<TEr??KN7knL*U5`Yr@>Ga##SaUPRa!zF!<D{X@vh#Shd6RpeVHNSejyAN z5p(Jv@@kY|bm$Ejqau>sa}bu?K^gO|@Y`Xy;P?dFv7+PB?VBgb_hn9I<`c>^nUrd_ zQx!sRjnjRdoGO-Aq8b+u>Da(jn`1jT_@IkN?C{@!I@F)f5xS`95WM&y`uqW|zy3i4 zbp?II`+vQkV|1>0p_UqV|2wP%T~YDns5*E;sNk7e;-C9hI6;X^^>APL2tVGaNm^Ms zI)Q;Ss8J^L3-VKi=!N#p0C!IaU9D#S!`N&Td?XbbEXm(icYX7rqAwI{?-eMK2S{RT zZ`YTyeId;TV(#Y{p8hNHdcghH>(D(V#Zm)7{@F?UOC7Qnx(oTKKR^FH_ArcSRS)mx z<bCRV;MxIO9%Bsd?F+BtKkpTnDdoujcbjyWZ9=+d3y1O1+neXgQQQ&LCH?TW0m+zc zHr;b3q5I_ETVOOY$MB?g&z&yw?%zA>a6o=y?7yv|Ljz7O)llL9j!;a-M8C=BPQCfg z&2is}1wsh^{L{!8#ZZfZQp~MeWD*7c^Bqt>*f+~aP526Kw5N=;H#gcNUlH>tK=mZ$ zJ$_1WZKpLUFL5)WDv0?{U!qq1bF<9{<;iVX?qFoAD|+v#GRuAH4~&DCJE{xn`%4DV z5PATmM`2N~K&4B8=2+05s|Ev}9l7?>wA?VT9_YVSoV7%bWMyR+&A6D1^slJJ=49S+ z(c?CM)*ykmX=7<uW;ZhwDiEHVlU*Lk%+0MPHW92h<GOEdwO$uAV%cIn0r}$zI(qPs zgzO>tqlYAq9+LMorpCuVZX7s+_j_H)Z%ya1$a1@&!gr5N%+2MK_)7FzxD!3rEIzpk z!b{n1k-x%FOU62g70UH{+v4+h#QdEIf`2R}O_0DHxlDrRInuPUs@RZ*(>PrQ@5OmE z4BUQKzT#|H960=8GAXYSr#m0jR?o<JyGtbAy`#P)++1v9`v_cKHybli*vbxNE5Z2% zPPse{+;3=S>e!b+PF|PG+bA)@D_}sg|H`k!N5A~=6Fd`02CMC+Mw>;$Z#6$sl9B&( zcHntFC}M#;zY42byNJaJ$9&wOW3BdmiLt+G;r`}NtC3Ghdf`vU92pkbM}VxI_iYKs z2J7WqQ@>Q%jB7oL;sPit!&2nlzkkKa`DgLX?p(=z!&Sg9MI9+Q@L7uo)*{>mirm!} z?W#S!G7J36_c=K^nhe;CX|oOPsa|(Ky<yt|&4Yf`C|kLvJ{L7)${|_2Hv#9eRuD_| zb}ZO6nVGDd77xcvd0=E|DHm8Y@rwWlZ&MtSCVUSlF4rZ62;5!+qCjeFHcxmq&%J|d zhdZRr$eUP}!)n$fPD|G5K2!ATvJ2qzLYYUTQIuPB<?EpX;wtT7It?%IM)J1G*GHI9 zyW{v=j%j2Z`*pC<&G8Dq`fTHgtI=Vf1B)2#3hRM4NX-T<5-O^euV25WK=HdIX=rHr zbPt%uYC~C4Eban#V@9m!^vcRgZeOx`Pi1z5zj5WXEbOhEeWLy-U|wrapBm}ISoKBV z0taN%?Vi)enkvAX<VuvQJ*8H#Ic#wCJ6maX{vjC7BOqBhrGB2JMS~M3g)fiwc1@@v zoRIVkIht^fb1*N12|Zhh_(r`^#a=&~(f}+;O6sAM(kVRK+bXc3_lLw5jj1vtKvs1a zRfIIgs*KlC1@J~(8fFTVZ<HnL5uSp15JX;Heg$eenxLYy0fweu|C|hunwt73O&Xnt zg5G!+t!BSReb6-DPQ`o1uco&tfph<P^|Hn-(B%U^{Oi_Cw)j=ln2UYw)|#Z`>rd&T zujglHdwWN}+^MLjkdggr`TX4UJ-U4l$hc%=zB0(nAA5hC*ztBdPinhoK0-esu}4T` z`modtFAngT8*F-gKBN~o=vte~PoKQ8cN~NXP^8RSS_L^F4Gp@X+p1oa!a1d&70c*@ zpycFd#1poooo@i!@Im^x*0o7*Zra}79?+VZXIEa(_4x<<_4~bdo5gStK4-fZz#THE zp0v^tW>6+Vl29aYQVT!+7En~kyl2CtQ@i{|JEN#5Mjc9qm{%d07#}azEtm$eB2E|$ z(1DHai_8-o<IaB%ozk;NidT2(Nu}kI`I|m^+h}RkcDqck)|0l`&jRL@=c*w^IOPo0 zJ+@=d1OY?XnYJ(zH@c9g*A~W41`P{NYy783YbeZ%y79fJZnMv|=8n$T-suhxVC*+n z=fl>VwjEs0fTC7LC>hK(id4??y?ru0v6Tus2Gy*4mX;uW2M0fG5vq?vK$)3_g*+P^ z9*&havoeZ?@&RvV(&5PGy*hkmO-^m<VP5BTLT3i?mqWmZNh0#uBmNfKXT95ZM)KBW z$=Ef+`nq%D#B0!i0k=%oo72iD`3=H!T=svHh`_B)d$hh|-T9_%Ux<MdC^0CHqs`PN zA=gZko5kRLVQy^uLEjj=$>aDJHc*JUZq8O0LE$1-vod3gTup3C@{jdUJ8-Aj3^6^8 zZ}3nix@sFOu}~+cbkv8;bFAg7xofw%+5^Tx*S&3;i-DKdKgU^;0&cr4{B-yY5QceR z-n??@J$Rx_cVz5ptL?Mi+Gr`_nfo(<*n?~|E@ko<jz85$az@GjM$(hN-erH17P0<} z-yv~amYx`&V$e~vDHSDopntkI4ULztQKk*U^IrE_I4;`=Ehm;ew#@Z|1q8HHrCCp~ zQsf%b$T}X6&qd`9G~wRMbxu!if^5)gEgk0))ZaZU2ef?28H)6j(PmHIXGA-{8BBKj zUK^Nb8e=fD7&Y(Os8Nj<&B1t3Kn<4Vmlu_0d`bw4NC;ZWH2;OVol!!?0~h{4R5<VX zoR;;)b+#?ql?VL{SjCD$Ws}tUEZjPZtIgjP?Cx^ejr!JFp>B`5XBPL5_gRxXde2Y| zDJhm2rxf_*VAbg=*pN^dHKJ|L9@FzgIIAQq4D|Zj1ztKBdh<~Eq&x~r#=*hS<{rnK z*y4JSNB4na*c$%H*!{GOzFiraU44C3zP~=E{Nk1{t16T7);WTUZ15JA0*v>L!BtR~ z3XYnMjmCt3_iv6k8$s7TCT(W2w%1cmgk6CHYn|QQwQd(kkR?Q4qZyy%Y_pIE@i|## zKxbhE1DiB3wF3$Mh<5iR0fq3gWTn+m7E~RPthx-e{>j(ON8#2B#Ux$@xiFY&V={9K zn2gFXAEWyeXfIQbo|CwRKZ_wJy+mxu^>hiX_0N?Ryy17Xkq+$1k$pNnH9a^37|8TA zc?Y#{so8rQ1FQE|Dy_!`Cng4(QqIDS*N_!_E+jigl=cf9;f-Dul|>K8$kfMkw4P8U z@#ASJwvG)kM5#lCLyk_NC>JtG{ERfYF#L+6o1K^_Nu$hEzWrVV7XJ|oS3a-c3BhWh zD`kz_0^I#gtmDdh%Eqm-nmcE4YhAa{CUL((H(OSIt#O{7f^x(gtjnHQ$J1$9sd(sF zm)*y;-POUtzEeMr#Q1mxc-S2v4US7qb=hszjP2O+mO9!p?SkNPn6y@N)+<*e{SOO} zQZAXz>?k8m!`bw`m8bSh{!<kTo4^<~HFKF34b+_q@0#6sbW1u_a=>j;{fARk$pO2J z8huq|6>1R^wtabYfwrco2EK_yz}kT}xvZ(~u0u9OiDR|STY0<<s+!VQPwz^7Wqg$9 zKzi{bHI-GQIn7+DxrgP>$)2#+I3{lF4v+1|fX{jpN|})2YJJw9N#?9*=?Q;t*Gx5> z|A*Vs_4&Z}e+%3Q!#v3ys*m%C1x=Ts)se+;EopUi1hUN-9G_Wn%}yPCHiL*VA2xH@ z8Gd65uJ-p8E)izJ1~eikLvfZ;^hWNb?_UF3%2I5Id8*4ety^m^!Yv$8%gLtKNWqh( zy=Nsy36h@A)a_*F)<T~=c~W;qk#IhjhZ2BZSSU2Hj$!59d*?qj2Q^jC4t>o1!}mDs zuiX^JCW&rXT_w&xbb(K2_#Wr)nwpw5?wQJ>>_Cg0QcvPj?=@Wor)CUdk`8?=Sy?=w zM<f^s48El?UYs#h<Ynw}2mu`=A(nQ9FBk`DfoC$dFNrx`U?tGf5p;ibMy*b<rcA3B zXiXg#yE@720-JF~rSmyM!~R@}%20yD+-a@P+H_y~VjRfL8r`>FaREx4i{9T0|1h%g z^FyM{rK}j08^*M#0`W8&t>$*!L$-WXE2XlL#f<wdKb<dyV}khk`PWysLR1UX%=i#> z^L5wno03Z&QzC_`%8S1FK#h!);^b_t$Mf3R7&%X3a&pYHv>3_FEyE8(fj>E$5m|E_ zH(hC&Q`Ydt&%>RCk%J@32fEdbhfI1S-MDU_^ru*nAu-pEP}tm{1YFsdw{Jr8&nS~= zsHnUk6a$pgWwvzKi}n0a%H8tPWTPXoCUFpphkGw-j@;v8zpYwKo(}%$zRu2A&Oi!> zzKiW#v$v3H>V7VD8osuN1poDTfB*XBVdlBvwl#Ls{jm`*?Z`Jc_eB+7H{5fY$vNG> z6haWVB~08VplEVCVg6}bPLO8%-QuJZ;OJ!qK5@n0bn65hk?+XKQ`AfJCJHAaKkER= z6X$7nC44qq0?p}I*4Riv(=O2`Fq8)#X|GH43Cg@nKc>4MIHXg>EZRY&93VOxiA~D1 zF}v|i7<-yzKm#&8<WxfeR88t9YHgMNlH&(xiTMfbF$%OI0fAl|8D6=<EHlm}Bk5e^ ztjAWlWgM#Ium<Nqy$UtX?#bb<jIy1A9}_P!3}Z=jrfU&7E*`pIe-NeE-B%!#Ca#)i zFjo$xds=BQG<>)qZ#`6nb3Jj>gDL%aUZmpCuW7ht`a*encNjNt_4t+Vinx7lYDI)~ zJaLcPTJRZH3|XMMz7%eBcRss+|9*E|T#AV#*Ig@p)Xetmz1&vmf>wk%e@%5|8DkPO zu|76)a$#qo8i&c((?he0n;Yc3eaOt|OI~Q#<*xc{EQ`g_!Ld=Cj&&zp=`~(6%hNFj zm<a-rsw}T!y4W$`S4}CXo|$hvf*hObNKa((X=((ujBexhn}xHvIxla9{ujI3r`qzU zG{oMV?P+iu=ckmS(21hsmVgtE*WBL~ujH|&tL)_VE_x$H=p=EjGQmzeJ<{?#sdILO zmw(dLVj_4=#~(;>(q@Ttj(>LPcMsPnL)*@PpLK8m>_=&-sZ#}NAmb+=#&j5=9&?DF z_PMGdv?@Qw1_yf!&?p#s#3$Ln+SqI6c>a{k^=VBF5G6K!FV9SXedxe1g3s5<TT8Pc zgCoX$I-7;vD>usNwW^(cP!a-tlareZDVLVy=ZrTvrRc31l`_OJ!FTn_ZhY*7)FyLW zlDpfZ#SD-v-2AF4m;OmCaKSr2*Kk;rb#_{8Zwo?;mpzQ7>OIp!(XJnUNHNBVS&mb0 z*i(;6S^;A&lP}o(0!_;gyFPYc^QJ%wWe}9H>hA-sOPzDXDap_+tj<tr***h*20p&N z(b4B6r6_c9wA@y<gw=-+(jfPYS=D21Tcv1gdDTW=SU~&PIs5F!kfS{1_4HVB;HhP9 zW>KG1+{L}PtD~L#Gh6Lc-m*VQGoBQU+S#MO9ZnmG+gho&x3+$;Ek#{^s_~a>@6|7j zxkD;PX=!NHE5*(@1~iKQ*L9mc@dq_H*TXF-$mH<Wa7~lEmCHKcxU<66?`~D2K%Zw| z`eqN5Vr9doVByLgNJboai?&b&68!0+bwJ5<Pg&U*>yxO$_YwG}y|P|F{r3|l!XSzJ zdjlBhog@h8uQ3Pv?{V*5;)Q?B5!2IjBno3n2|vKU4}vl;_{s4$1`>Yh|G1fNFdU*J z5oR>Wan<1qR6dT5<v`)~qoTq9Lt!--&M<iMj)MfLKJb^q@*?qnl3L%AJO6Xq{y!TM z_NG~MPd2e`ecvya^T83n(e76gpe*F{+|Qq=C(*|J`Nep{j#aPRj|8$}R_uAcihTp* z(Oa63m#<N7#uv?bxNT${-^VyomBBg?fy%L5S`&z%Iu>xiuA?)3kas$EWMa(h=JK>P zFF@snc#b6db>gZ832?Jxzhs%HeNAcHtuYwmKJ)h!fmR|~Hjb*>C6HC)u@?hM{llZP zy&7w~7|#Qh(?7|ga{idK=(%rW?xRkjEMybhqZ7u>@z#A(C*3}Ysr!A8F+AVf6EvTY zEP|aX=f|~J0(A0q%PS`7a-481IlZ6c6eI=e5_NC(7<Fg3GBT-q_&S_L5#z32srC0m zD$1*Nx8_G@M>m_htijCwifbG`lWi&<h|8(Dxzh<Q!|i}Cbh!Y@i+M6s;wLQ*y`YoI zwi%BMEqXd(IZ;osa74>!Hh)x9PKO6m=zsZ|!4xqRGN4yaC{0a5UU=zhZE9p9Ix_gk z`_x+19x%LVrY&K{T8U3vE^C==!!%wBJ50CqAT*U6@}2n{wZyz*HlclMJBXpDdQB^2 zBxDfX@p=%0bw*NnUZY~iCenkT@^~ibCB(?z_?GWjSXYa8GFlng+Gg!CTDUz5t0cpN z%zGUbhhGm*8E-*8sJWcA^7)`%<%E}e`G0VnxW7NP?<c9MCk%!EK7~FKs#hK!&gY*2 z64Bu6keCVDM<#8=B0OnAGS>?*^x<)w;nEPT1d?2f+d^ePe672KE~jwzCmTKW?Bqd1 z*U`nJu_kLKHvQ{wKRU-{x1B6!&UZ-$v=J0-tu$KkWUf|gY9$5&H=8Ehu&X-)Guo1E zt(1H#uM6b^XNlb0gzGo$+1UA0Jv{nmt*Wea1Q47+a>inA8Jie;Mg6?>cdrEy&K@Bh z(^4bs4_3LFa({I(!0@ZqEG$RG4KG{>?zlk2=CIl*+MEXhJ-ZSa$4<)iC)A{@*N*9% zkFTO~KV=XGRRl-%HB%IwI`tf6QXp|sYuzqY-J(A=7E9CA&8H*|=vL8iFeP~iaH`3x zo43s#ZWMdW{k;4LG*Y1J=RiMcIOTXsS{#2d2Wbgp?5ABIX|Z)R1vmE~*=YG*ZyOnj zIeb22YiVR6cHlzoxf^t!F0`9ey73b70>di%C(a@S=_>Q#LuMu!jdtVOVfmu%<-pnY zWWAYc;J=$cTXIX_BK8Sk?5Igcb@+IZVySz}bR5L(x)QD7F3mB6Yl?Gb;kcCeD7$dM z`)QB;Y?#=_USW@t3!u@z`;o8Fx5=;aj%R^e%ax)N>)=#?fB`uig8!O{XFpCVTjVQ9 znzEg=_q;$hgyi^8i6nXsre?-Lpdu6kdF2g7B;;1=w$mclflIOLw5Z7>J?|D|1qTNg zzti?K(5L%cE<7f;-9uF)w#gSV8m6C;Z7KIJR2;7^CuH*NH^^sITrj_i9!QYa|1wB> ziSuRn4a$9xNy`>z=4h8{trGFwh_IegcxN*Ijg|*(cB@!!arov&>Hw|J6iJ=O<*&um zy?ox8*%OZn+v_J(V!F6XHFN@A7jkpkBuRB+M<?!Yqg0*fA!bI=*A&9uOY6S-wieME z%)$<5{IW9ooV-&pKFF6&14AJ%BW^?#V1Dvt3+Bga;IEvl-*F2hJzW!`NuBV^1#0P? zF_=5pHtZt>XWY*P(R_B}=@R??I*60K$?ni#+_#^er->kdq_&qdMDonFngMdSD{kRn zb>~tcFlyNq-rq-V+oM}|{0*+iS)`w0g*Eysp2sx4n?>q_Y>9foQM#V(cVj|UH@A|D zjf391et9zzloc*LOLluno&ts19v<r{msdums}pJMV75E`*PDDt#Z^rU-#M1sMkF;N z0hSH>072{5%g9LCs+8m}b4rz)hm;c}|6JaCq);Lu?vWImdbl_gQYu!<4tVb)yU)(c zzl&l+w>e#kB=nQ`reon-^SjgJ`WKS{&boR2!3koQ71HlzU;txm@FMb%#{S?z7WeJl z2sJhBjOq_|E^g+`Ox-3mQ#t|b>~M>rA7)NAo(dMcQC=mq*}?jm9%B~ghsWs$osap2 zW7(|bep#A`xaQxUXF50~(bUwWaC&Yxm9Axqqf1VX^p?}FzOO2~Mg+v`K%4fA3x9ts zHtdq8fx_n_6zXgvRsQ(Nrt)1*ZsFIjH7};)rSQjz8DM&{`kBKK)s}uwqq!599Rv6$ zHSS_lq6watG)TtGGh{+Xj<!Pl)MU5o!FqfxdsW6HaqYMHK%OCSyMmQuuXygP&zc<x z{h%N5^~tQbvNF|kjk6}+YoXb^;(O@I!3PhQfEaF*P5-nT|Kv?H6zC(}xi(sFNZ0e_ zO*q@>&ohd;!cn=Uax3u}m%eSql=K8`Jfzj-_qPx!MQo;!e%q%?1n;z^nMy<u@|b#e z0=|S(OeR?M6EhU3+uLZUlhPi;DJm>fl8{)VkG|J}@ZLlAWUqf%^5wY9wVr72lu;pF z+@74RJHLzG^<$lPC{j(3`sa>EEvZ?%?lgDSSf4rH7?Xe!Wq<?wb!>h&SSKv!J<T58 z6(2RPb4R?mK5N$%nPZ;@rTnX#0WZE4_HS_JM=<KLZSCi+)VQBG4Qx&Ot2N@g_iq{K z_V4b^Bx_?*(`Ls%ell93YTEJ3F+2@Brt($MwvBPxPK6==@X*pg%W;MmHdC4;8#{Yh z;?Hi(gQQO02KKTRQjvt1?1Y3M!*?poO!1xZZb9#~GPh@1@Jt2yJx5%FkM8Dapcm*3 zYszvIfg#6+4HVzLviDC-aT>p55ob%AGV-g1jaAJ34+}`YPf2`)bbiN7J_VDg-8sOv z%4Op%$jguLR?veE{2qM|QmTcMlOq$aZ>EBBhU@*R2VmtVpeSgZDMzK@6a|%L=ovZS zUJf?V*ImoK173&RpK@G2ZC$;xcsIm=r{Zuj?rl7UTgFrRP^{1T3}M+#o#c-F&sT=x zc7ca4w<|{OvvFDKSl#{`J)iv+Dx*^;XXdLq*HN6@jXE4!33#pc9S_qytaf2)TGpk> znj~PAFkV?WTLvi>^h=WO+Q$kM6G-Akn-vXmW~Id;LGPB=E(@=HoVPrI&c|_Rdv8=I zQ;#*qWp=yvV-4bwuLu1no?puv3#=@#!CZ$dg@dy=&BSA6Z!o+9{XA#8lf6U5o*ylE z$^F7o>fX>8A=L1qW5qh6`|9M*;N#l?6D`wYDO}eAL+a1n;07;5T<FQ)(w~+J{`=+a z#mB1+Iq7W3qap_=>|_2<%<I0)`UgnXk(}ggAJNUEyvbmEl1+iP`C22lc+SVjrU`ae zz~nnWLmC#vSQykTdo@YIbWUN$=+dJ7*8ke;89EM5v2JPg&vlZX*?x4!Np&#T=!z~m zmX#N4j@ct3q(^CtQ49Oeg3W{P`eVjy#dTf%%cQd&i%5fxDZX0R|FkGZ{)(m|pl1SJ zpk6-Q8X?rz_cd?y^;NE_Q7LB=NoSIF3ZwMIUIzRS>$9G8YorGkllMnbYo>cMN2Vq7 zXW75u1qXSMKo?_-?fl!xm_->cA{kp@4zW4FM^ic=hBE@@O_?Wixw?)Mbp$9!d4;0L zUCs%nY6`#v9j)mrjsNC-&8Mp8l?@p(S|PmHQ93#8J_sLJ^KS_1Z;;6k89rp8=X^U) z_zc;uv_#esmjOAyRr(Hvl>J%*dw5AWBRv)QJKsn2A5~59w-F7YBwi^Di?_k1LfG~b z<t!Aqq>m(vG_Ih$)Vp(%^`0b#5#>9$dA?Afd;N0NiWI(>^?`z$P~l?(KX;}GQsp2T zJcm#G>%9@KBJ`ZI?CNOl>iVl$ZOue#np}zYR+5iMPm6!Q3=1}<=gu43c)lD{FJ$h^ z8z6d0!EnvEF#MbMz*J*j^WDpccljQcfUS}Z#j|r4Iy-S!8flAYdVKCz&SR<2(lUam z%`E}pCmck{zwc}p+`esq%i**bWxiOv_f*J()N}Ksa&ywn$`i$WzNr}=u1PWDcw(gR z?=J4x$=BXTY%RU2{xre1p}GZ>!d`u&lRwI$SQa$KrozGku6Oy!DIUtEH99YVg}8x# z%oHK|ohkyXoiNKS85gz!NvE$p4KjFmp*eU~6hct+R>-&LiV7-o$QQ}>3R^XK;a@|J zJeF;dN=mNJ1c4#`X<e;n#9nstlLy4H2??g=ECNZ@kz&{bmsj{zX%3*D!sI;DBCXG8 z@LE$KWDdHDK5*CaXcs4dOzW~d9XG;OYZj8R9?KB-8;OJHBRt>FM8?E8&s>(G9MVmF z_JFhhCQKNt_KbM4JL-te8gJD^WlP(+tbKAg-RP6|((+A?*Il7?-k{$$deH&wANAY1 zK;oj+NI%hlpPs3SnXu>4@_0kXZWO;k?0S3Y-JlA!8cATB8dASN(P*jo&Q-w+>o;d= zoW($ms~oqlAkM?izkhzAJ(_07N4}`O6yffrhMrc1cw7j15>Eb*Q;i{7O`R)pSpzy; zfXa=wnPONKf}4A;w}>|8Nk^Yfmji_#D4q?EHs|U37it}PB<UZ-a`CuST>cce19sKS z+D+=rv4gM3kC3j`Fo&{rJZptFWrlOE1fxs5Apd^8E?h^a(K}7!rF|9K^~z*p1wLoK z?y*Pd;aw4QZNjon0j;x@ioHsW46=|a@u@Z<Wi9RQJ8_jZYdQXE%bzYi5BaxM>CIo8 z%21Nq+R{SrH*eUL>{@BlW<xH|kE^#>$W3A>*i}Aa-#h^}f1iM#j!6<7Y_qdJRvvrd z2!cI3;7$vhaT^2cDs12RG!)O)H*Iu^W1-nW>DD(lPrK*4`06U+M(%fD>TEgjZ=+Lx zw8=4KXRI(fA;#-!dM+iff8^0&!)y|)9=jsa^J>;dq-^7t^2F7=7bEKn;19|4sUE)> zlrhFB?KrM9LAZSE&g=K1V+pV7FXxTpEG>!4aS%4u?h|*tRmOr{7VzNSU?(&ZXE)-a zxRLL5PZO3d)^o=PF6@pRCck?9UMg+uXX-t$Nt5Wvj@*W7uBE1Q6P{YBe24Go-uu|i z2_luV*JrmI4yfsJ6B3`Ua-HpE*C?$?Yz4Ar(_;?!y;ZlF1kNf8V=*ta&htQIG1@dT zAn&$b47zeCj%opKM@=Z4?M6v3s=03$(>)*eJwE=OkP!J9P4uj{0j$EPMK53M{?=I7 zE#jaOD9{shLGLRKeS4qPF|d%Y*p~6nUyHVAh6nQHs^(%rY^d-u`)gmNyQdN)#q-`j zHa6R=V#x0xY<j?FfZpb1TOs2h9pjDAw3fE!D~wY$r(Dqj!3^x&Kb>ZYQ$MGDrcWcx z6~$_Pq<G^cuf;u(B=0$}umcDCe*ZFg7h^kJBN>w=yzYpK4gc58In(6ld)@-OkT1zE z*&hzL3&XRag5LMhtNJ(pRCr8Wr+p-+zON!P$NkO}gT5{JIff%)kV8dw3oE&{z1xlC z=A7|7Rb&4M%Xx;^X$T|zgfUS$T~)=GWWeQWvF$&Igcz!W8kl{e5sd!?BiahcP)Psz zGBstCE#batdVlLCR63o$Q;bdC6g$4wcGMj(QrRjXu37x0p@c+LY%J}yns?qB$B$uu zdI#Rq8x_T3NYtYj#3LokubgMzeSgdL4QvZo#1(F>Zx+0ZsdiN>#JT}2M28ge(Tw<j zH_v!LVt_$k38W=5w>0R9O-xKItgT5rS(6{$RHp|Q!ugZmVq@RTagWkrq!Tj*E9-D_ zzJtLuYasq@^8~fL(>I_fF=1O+Y`2u33ZtxS-{|0=0r;Bgink4rjEdBR)U-Ja^XlK4 zE*eqypLt^pNZcH8UbZU4dXXaeR}BO@99UZX<NwyF4I`bC>3h;Mg@+o<u~|80o!Zf< z&8$siq;lRjKkoeVDw-*~PL#=|HZO{nG=z&QWc^kGU>;;2VjFz&1woIUy}eTM1JOL) z1RZ(EKVR*Z`_h}&XeGo~eoHMat!GFR425ZuIoo|~%uhKvcf6Y7BVQI^F?nD9+wuGc z*z&Zm5l{(6vwGXeEimh9Ye#jH+{Z|-U>YXUefRPOxi$xTSyUu-ZLJn@kgD(}a`QJg zm(#Pc9;xDqBl9wXnx?DYlT}$_oCyq-ReBzQi<8^f&`?&3ou{XRgM*e!m4>j?m4?eK z?v1!=@tDD-fLK6%^p>=gR7i+4DX!*+4^40EWqb?`ogeTltnk}zoXfvHf14J0jr(YO z9Ya*OvHws`Oe`xilO;xKWkq>qIg<WvZmzkRsi}0fQd`m=IcGDS^3Se3Y7PzoAt5R{ z1T779mJ)|^`@8Q(t`h}S+CS6!o8s;h2(2^wtlh7e{69TidpHwp8{gi(P7}T4EU9$B z*vd<em3W6*N{MOCH94Qfhho0MWhv&BG|Z?Kwk$+x*qa<?Ybz8MlR3RP4RhKs`-Z;n z@B6y$>$!f<eLeSm-Oum--EWSO^EBQStuB7=du-C(9+slihJ=n<dYuI5sC!M99{)WY zhf`lD=?5#rtEx&BrMhT&jJD0T*WZ4FLOrNbf-&ko`JFm4pJmBR44AJ#HE`1wDIYE} zU)LGHuR=TQEoe&Yq+npk+?c<-_9S6`&#ZjodQajt+17nM8<?XPVz7_SwtDPAh3xkY z8}8za`%QJb1~~2~Rp!ER+A;|pWx;C^{2>`zU*2_AuPTDmiwmj~a;^f~8qGdw-fb#L z%62gGKa1$YID`lwhtE8`bxq3O>wE&>lB#^CiQly*<o+>cio;KhABmkY-Usgm#oB%= zXxQsZk$4QgclH6B+l!P!X%8n|u?~U47|lR&yO?4|Laa~s;;D7BsP6D*`h2KVm#~fj zNz#6G-Q|?8yE%ASL&efLu#6PnV*3@le&?xM_|tquNuxSI_&)sOMpyuJAkjvyGl9W< z;<t-Gl4hb}S~IN4CF==h<%IamC;TaGkBEC7Ak<RpCr7tj`}8*Wt>+&P*tMrpc2wrd z&7^$1AKl%LKaD@a<s#xw=e?rN^JF5N*$TOI<Ql8ywO?`9^Yc+eMgQX_*-In{$>Tv% z?g8~#zRk|B-mBsPxwDx*$(3ZgZ?QK6E`U$R1ZCumtyHGx)ZT5o_m=eLn_KN#eJ3=N zK{qn?_1*#4es61)LkoTO`&B)jlrWtdyHbU{J)kkE#p0({-u8H2YfHw;Y9NY2Sm&8J zSD=l9H@Z-WuEkOYq+zgBzaX!8KYr4UnHyH}5!YL_J9O&8m$V+cQGE-o@Y{^aA~)uD z<A0HpZ{w?-9P+b8`6$kb(Kw{Zl>yCJpBiGqogbd{4Si2!E6Q%*9-Elztueg5Mv9LH zy#uPL609)X!o3@aN(J^sBg}fE>YKOTD@xvtEp@I^IO&IGSRx|~oQ*=x{}^p}Q5%+o zzr&cyFjM#UIZiY$uQ+ChbsnE<g${GxRQGcc`bW+!b-!xbJlw{ltr~dOX@@aSY{)Y2 zg)x>NNAHel53@6>#TNSCJ(|Z+HI(RdGhr>wo$kvI9^1Qe1$zi1E2TEge%71RTy)do z6%V%w&|Z=Ob@^MDf5k)X>MwW0uElNRfTM8&1vfMMmxVmCtS`|!{-GJ;4Xg@CObcRL zO1nBMBaQ(uCr*27e*J($R>Hxe>txy9I>4x|*^4#P%ZdN?maSbd4;rRlfxU0|OR#fv zZN!v6t=v)T6jw*aPsQKVbT(mJsek_TQv<yw5*39UH1yW_fDx!!{aWSGsNRyMG+zp~ zEP1qmJkd8~K-G<A-z*q-SnhNQ`5}vS=5zYds<)JVPyYdP(A8z+?)QC9YGSfF0VyNe zmtAENbk9=P9v`~3$fztIt76R3mVZE;8pB0@@AOl1w3$$9h16%s`e8WJjvO@cK!QA} zzG-0sd40t++h941%Qj!$+2IbXoH-Nf8Zypn%Lr_-3DTw9;rC3*6uih{g|72-=bA`A zy+mucB-oaGrepL-q4zkoO_A1vICd(PTxkGjW_wtXj4X&>x@?HW>EyMm>>Im#VxcOz z!kBc2X;h|4$xpa6-ZFy6aFfl0aRx*g%Ig>TbqA=XDsY$I_56xKvn}$SaVlno+Y8n| z7a;P2HCx{?Dm<0hqw)Mi%1fxiYfnhkMT^&|81tSt$AX&=aolM?a#+58&E<L3&-Yvq zXt|!mu*?rH(Z))}))6m-K@f?r1R0mN@inv&lmIVeWF)Sv>>NL)aTqQh>{nS=8YQAz zy6GPa(&gL+c-t%v>)<&^lBcvB0SY$s0yIvdZ-6OY;Uf8iZ?{S#m%MP<6C2JHHCv?f z<Q_hY3x;^jxKEZe@$7+fD=7+p&Y2WSL^X}mzoM?>nX3@C;%qo-^mY}vN;HTv^Z^ie zOCC^!R7p+ZTm)z{7bt>=P6Q=tz2J{SQALM#1BkYsFRj3uppoz`dyWEz>n@T`l@MHV z6v8@slI1s9=mgadY8?Qg&D>hHOy;?bM2e<Zjh`$AI3jgV|6HB4-oD9C$K-TALn{?! z{01PVcmDBNF{z8@qF}pa1Hmp`M!=3E=GILv^4fJwMQVf`ti%&MAS@hxnn;Ar!c@0( za1=q0hMR{hSEL1|+(|%Ar~q0oSSf!Xr{d1jlHXcCLzpf7Iics092uJ;pANi`+)txp zXft|AEFvsYVb}lMBwJ+_8nC(e%hM{yt+(aK#fc$cPa)yHoJ#Z;@04FEub>qBBMds7 z9Td!`T6JAs-wrrhI7Ep8&iv+(CWE!7gVg@-4$Wk<*%Ez!{7^`wBXEb#c7ce<9)?58 zT=kVfzi>j804{Pl3k&4_X96=51to9v)q_P|evfJE0Rh*oiwV0D#S-buC6UA_2VRoG exMSn?w*jUad;e;gVs4Uc&%-YLSz+z{pZ@~-(~GA7 literal 0 HcmV?d00001 diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/wrong-password.png b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/wrong-password.png new file mode 100644 index 0000000000000000000000000000000000000000..bddd25b0245e84d0cbc340d5977462130dd9c7a8 GIT binary patch literal 52191 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 H$-DmxSAY{` literal 0 HcmV?d00001 From a3e1bbff08ca95725f68f5aaa6d8b33b6b001b14 Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Sat, 8 Aug 2026 08:16:34 +0000 Subject: [PATCH 11/14] =?UTF-8?q?docs(qa):=20=E6=B5=8B=E8=AF=95=E7=BB=93?= =?UTF-8?q?=E6=9E=9C=E4=B8=8D=E8=BF=9B=E4=BB=93=E5=BA=93=20=E2=80=94=20run?= =?UTF-8?q?s/=20=E6=94=B9=E4=B8=BA=E6=A0=BC=E5=BC=8F=E5=A5=91=E7=BA=A6=20+?= =?UTF-8?q?=20git-ignore=20=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per maintainer decision: a run record is OUTPUT about one build, not source. Removed the committed console-login run record + evidence PNGs, git-ignore runs/* (keeping only README + .gitignore), and rewrote the RUNNER/README guidance so results live in the executing environment (CI artifact / tracking issue / external QA store), never in git. The checklist under areas/ stays the durable version-controlled contract; a run is a transient assertion about a build and belongs with that build's other artifacts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YD9f6FYyMraUWYeJf53V43 --- docs/qa/platform-checklist/README.md | 7 +- docs/qa/platform-checklist/RUNNER.md | 16 +- docs/qa/platform-checklist/runs/.gitignore | 12 ++ .../runs/2026-08-08-console-login-demo.json | 70 -------- docs/qa/platform-checklist/runs/README.md | 51 ++++-- .../2026-08-08-console-login/after-reload.png | Bin 73903 -> 0 bytes .../2026-08-08-console-login/app-shell.png | Bin 55350 -> 0 bytes .../expired-session.png | Bin 74142 -> 0 bytes .../expiry-cookie-only.png | Bin 74520 -> 0 bytes .../expiry-full-clear.png | Bin 44798 -> 0 bytes .../expiry-probe.json | 132 --------------- .../2026-08-08-console-login/login-form.png | Bin 50149 -> 0 bytes .../observations.json | 151 ------------------ .../2026-08-08-console-login/re-login.png | Bin 74517 -> 0 bytes .../wrong-password.png | Bin 52191 -> 0 bytes 15 files changed, 61 insertions(+), 378 deletions(-) create mode 100644 docs/qa/platform-checklist/runs/.gitignore delete mode 100644 docs/qa/platform-checklist/runs/2026-08-08-console-login-demo.json delete mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/after-reload.png delete mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/app-shell.png delete mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expired-session.png delete mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-cookie-only.png delete mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-full-clear.png delete mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-probe.json delete mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/login-form.png delete mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/observations.json delete mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/re-login.png delete mode 100644 docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/wrong-password.png diff --git a/docs/qa/platform-checklist/README.md b/docs/qa/platform-checklist/README.md index add7613328..ee1e7002f3 100644 --- a/docs/qa/platform-checklist/README.md +++ b/docs/qa/platform-checklist/README.md @@ -20,7 +20,7 @@ docs/qa/platform-checklist/ RUNNER.md ← how an AI runs the checklist accurately (verdicts, oracles, evidence) areas/*.json ← the ledger, sharded by feature area (append here) coverage.json ← capability-coverage ratchet: every governed metadata kind → items or waiver - runs/ ← durable run records (one JSON per executed sweep) + runs/ ← run-record FORMAT contract only; results are git-ignored, never committed ``` Sharding by area keeps parallel edits conflict-free: two agents appending to different @@ -172,8 +172,9 @@ matrices still rely on the showcase `coverage.test.ts` demonstrability gate. A release no longer gets a hand-written checklist. The sweep for `vN` is a **filter over this ledger**: `since == vN` (the new capabilities) ∪ all `P0` (the standing smoke) ∪ any item whose `source` cites a PR in the release. The tracking issue for the -sweep links here and hosts discussion; results land as a run record under `runs/` -(plus findings filed as issues, one per failure). Item text, fixtures learned, and new +sweep links here and hosts discussion; results live as a run record kept OUT of the +repo (in the CI artifact / tracking issue / QA store — `runs/` is git-ignored), plus +findings filed as issues, one per failure. Item text, fixtures learned, and new traps discovered flow **back into the ledger** as revisions — that is the accumulation the one-off checklists never had. diff --git a/docs/qa/platform-checklist/RUNNER.md b/docs/qa/platform-checklist/RUNNER.md index 21669ef948..7d07f88fb8 100644 --- a/docs/qa/platform-checklist/RUNNER.md +++ b/docs/qa/platform-checklist/RUNNER.md @@ -90,9 +90,11 @@ test-run output the clause's `evidence` field names. ## Run records -One JSON per executed sweep, committed as -`runs/YYYY-MM-DD-<slug>.json` (never edited afterwards — append a new record for a -re-run). Shape: +One JSON per executed sweep, written to `runs/YYYY-MM-DD-<slug>.json`. **Results are +NOT committed** — a run record is output about one build, not source; `runs/` is +git-ignored except its README (the format contract). Keep the record and its evidence +in the executing environment (CI artifact, runner workspace, the sweep's tracking +issue, or an external QA store). Shape: ```jsonc { @@ -121,7 +123,7 @@ re-run). Shape: } ``` -A run summary for humans may additionally go to `docs/audits/` (house convention, -date-prefixed) or the sweep's tracking issue — but the JSON record is the durable, -diffable truth, and it is what makes "this item last passed at revision N on date D" -answerable without archaeology. +A run summary for humans may additionally go to the sweep's tracking issue or an +external QA store — but none of it lands in the repo. The durable, version-controlled +truth is the checklist under `areas/`; a run is a dated assertion about a build that +belongs wherever that build's other artifacts live. diff --git a/docs/qa/platform-checklist/runs/.gitignore b/docs/qa/platform-checklist/runs/.gitignore new file mode 100644 index 0000000000..14ae3f9a35 --- /dev/null +++ b/docs/qa/platform-checklist/runs/.gitignore @@ -0,0 +1,12 @@ +# Test RESULTS do not live in the repo. A run record and its evidence are +# artifacts of one execution against one build — they are output, not source, +# and committing them would turn a version-controlled contract into a dumping +# ground of dated PNGs. Only this directory's README (the record FORMAT) and +# this .gitignore are tracked. +# +# Where results go instead: the executing environment (CI artifact, the runner's +# workspace) or the sweep's tracking issue / an external QA store. The checklist +# in areas/ is the durable source; a run is a transient assertion about a build. +* +!.gitignore +!README.md diff --git a/docs/qa/platform-checklist/runs/2026-08-08-console-login-demo.json b/docs/qa/platform-checklist/runs/2026-08-08-console-login-demo.json deleted file mode 100644 index 7127fad427..0000000000 --- a/docs/qa/platform-checklist/runs/2026-08-08-console-login-demo.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "run": "2026-08-08-console-login-demo", - "date": "2026-08-08", - "scope": "item:platform-core.console-login", - "app": "showcase", - "env": { - "framework": "10c4ea997cae3e0ef518f2f0a9bfbf23d456fe5d", - "objectuiPin": "7dfbeb704e1eace5dc5eae1d6168c4ded805a32b", - "consoleDist": "built in-run via `pnpm objectui:build` from objectui@7dfbeb70 (bundle canary 'import/jobs' present); the vendored @objectstack/console dist is not part of the showcase workspace closure, so the first boot warned 'Console dist not found' and 404'd /_console/ until this build + a server restart", - "port": 3477, - "db": "file:/tmp/qa-run/data.db", - "seededAdmin": "admin@objectos.ai / admin123 (--seed-admin on empty DB)" - }, - "runner": "demo run — opus CCR session", - "results": [ - { - "id": "platform-core.console-login", - "revision": 2, - "verdict": "pass", - "clauses": [ - { - "clause": 0, - "text": "login succeeds and lands in the console shell (nav + header rendered, not the login form)", - "oracle": "screenshot", - "verdict": "pass", - "evidence": "Typed the seeded admin creds into the React controlled inputs (native pressSequentially); inputValue verified verbatim (email=admin@objectos.ai, passwordLen=8) BEFORE submit — the automation-input trap ruled out, no auth-POST fallback needed. Submit → POST /api/v1/auth/sign-in/email 200; GET /api/v1/auth/get-session returns {user, session}. Post-login screenshot /tmp/qa-run/evidence/app-shell.png shows the authed shell (ObjectOS header, search, notifications, 'DA' avatar, 'Working late, Dev Admin', Your apps: Showcase+Setup) at /_console/home — login form gone. Pre-state: /tmp/qa-run/evidence/login-form.png." - }, - { - "clause": 1, - "text": "the session survives a reload — the first authed API call after reload returns 200 with no redirect back to login", - "oracle": "network", - "verdict": "pass", - "evidence": "After a full page reload the console's first authed requests all returned 200: GET /api/v1/meta/app?id=com.example.showcase 200, GET /api/v1/auth/get-session 200, GET /api/v1/meta/object 200, GET /api/v1/meta/view 200, GET /api/v1/auth/organization/list 200, GET /api/v1/auth/config 200. URL stayed /_console/home (no redirect to /login), login form absent. Trace in /tmp/qa-run/evidence/observations.json (afterReload.firstAuthedRequestsSeen); screenshot /tmp/qa-run/evidence/after-reload.png." - }, - { - "clause": 2, - "text": "an expired/cleared session is answered 401 by the SERVER on authed API calls — the deny side of the auth gate, proven on the wire", - "oracle": "network", - "verdict": "pass", - "evidence": "The server denies credential-less authed calls with 401 on the wire, proven three ways: (a) boot-time unauthenticated curl GET /api/v1/meta/app?id=com.example.showcase → 401 and GET /api/v1/data/showcase_task?$top=1 → 401; (b) in-page fetch after ctx.clearCookies() → meta/app 401, data 401 (observations.json afterCookieClear); (c) the wrong-password negative's post-attempt authed call → 401. GET /api/v1/auth/get-session returns 200 with a null body when unauthenticated (better-auth convention, not a leak). NOTE: the console persists its API credential as a localStorage bearer token (auth-session-token), sent as Authorization on its own calls, alongside the httpOnly better-auth.session_token cookie — so these wire 401s are the server refusing a request that carries no valid credential (the deny side). See clause 3 for the session-model disambiguation." - }, - { - "clause": 3, - "text": "the console reacts to expiry by returning the user to login (or an explicit re-auth prompt) — never a dead shell rendering stale data as if authed", - "oracle": "screenshot", - "verdict": "pass", - "evidence": "After a REAL credential clear (cookies + localStorage + sessionStorage), navigating to /_console/home redirects to /_console/login?redirect=%2Fhome and renders the login form — /tmp/qa-run/evidence/expiry-full-clear.png (get-session 200 null, then the login surface). AUTOMATION SELF-CHECK (RUNNER rule 2): the initial run cleared cookies ONLY, and the console stayed fully authed — its own API calls returned 200 (meta/app, meta/object, meta/view, data/sys_user_preference all 200) and the shell rendered populated data (/tmp/qa-run/evidence/expiry-cookie-only.png, and the original /tmp/qa-run/evidence/expired-session.png). That LOOKED like the negative's 'dead shell serving stale data' FAIL, but a focused re-probe (capturing the console's OWN request statuses) proved it was a genuinely-still-authed session, because the console authenticates via the localStorage bearer token, not the cleared cookie. Disambiguated → no product defect. Traps ruled out: stale-console-bundle (console built fresh from pinned objectui@7dfbeb70, not a vendored stale bundle), automation-input (typed creds accepted verbatim), shared-browser-tab (own port 3477 + own file DB, all fetches pinned to the absolute origin)." - }, - { - "clause": 4, - "text": "re-authentication after expiry restores a working session: the same authed calls return 200 again and the shell renders current data", - "oracle": "network", - "verdict": "pass", - "evidence": "From the post-expiry login form, signing in again → POST /api/v1/auth/sign-in/email 200; the console's authed calls then returned 200 (GET /api/v1/meta/app 200, /api/v1/meta/object 200, /api/v1/meta/view 200, /api/v1/data/sys_user_preference 200); URL back to /_console/home, login form gone. Trace in /tmp/qa-run/evidence/expiry-probe.json (reAuth, metaAppStatus 200); screenshot /tmp/qa-run/evidence/re-login.png shows the restored shell." - } - ], - "negative": [ - { - "text": "a wrong password is rejected with a visible, named error AND no session cookie is set — a silent no-op or an error-free bounce is a FAIL", - "verdict": "pass", - "evidence": "Fresh context, typed admin@objectos.ai + a wrong password → POST /api/v1/auth/sign-in/email 401; the UI renders a visible named error 'Invalid email or password. Please try again.' (red banner), the form stays on the login surface, NO session cookie is set (ctx.cookies() had no session/auth cookie), and a subsequent authed call without a session → 401. Screenshot /tmp/qa-run/evidence/wrong-password.png; trace observations.json (wrongPassword)." - } - ], - "issues": [], - "notes": "Item verdict pass — all 5 acceptance clauses + the wrong-password negative held, no product defect. Method: driven in a real headless Chromium via playwright-core@1.62.1 (launched with executablePath at /opt/pw-browsers/chromium-1194/chrome-linux/chrome since the pinned browser rev 1234 wasn't on disk). KEY OBSERVATION for a future item revision: the console is a bearer-token SPA (session token in localStorage 'auth-session-token' + active-org id, plus an httpOnly better-auth.session_token cookie); the item's step 5 ('expire the session: clear the auth cookies for the origin') does NOT actually expire this console — clearing cookies alone leaves it authed via the localStorage bearer, which manufactured a convincing false 'dead shell' reading on the first pass. Recommend the step read 'clear the session credential (cookies + localStorage auth-session-token)' so the expiry clauses test the real deny/redirect path. ENVIRONMENT: the vendored /_console/ SPA is NOT part of the showcase workspace build closure; it had to be built in-run with `pnpm objectui:build` (objectui sibling at the pinned SHA) and the server restarted before /_console/ served 200 — worth noting as a precondition for any browser-surface item." - } - ], - "notes": "Standalone demo of the RUNNER protocol against a single P0 item. Evidence PNGs under /tmp/qa-run/evidence/: login-form.png, app-shell.png, after-reload.png, wrong-password.png, expiry-full-clear.png, expiry-cookie-only.png (+ observations.json, expiry-probe.json traces). No issue filed (no failure). Rough wall-time split — environment setup ~30 min (pnpm install ~cached; framework dependency-closure build ~18 min dominated by packages/spec DTS ~2.4 min + downstream DTS; objectui console build ~8 min; boots + health ~15s; diagnosing the missing console dist a few min) vs the actual browser test ~4 min (two Playwright runs — the main driver + the expiry disambiguation probe — plus evidence review).", - "evidenceDir": "runs/evidence/2026-08-08-console-login/" -} \ No newline at end of file diff --git a/docs/qa/platform-checklist/runs/README.md b/docs/qa/platform-checklist/runs/README.md index bcadd638bb..8179f683ea 100644 --- a/docs/qa/platform-checklist/runs/README.md +++ b/docs/qa/platform-checklist/runs/README.md @@ -1,19 +1,40 @@ -# Run records +# Run records — format contract (results are NOT committed) -One JSON file per executed checklist sweep, named `YYYY-MM-DD-<slug>.json`, -**append-only**: a record is never edited after landing — a re-run is a new record. -The record shape and the verdict rules are defined in [../RUNNER.md](../RUNNER.md); -verdicts are only meaningful next to the item `revision` they ran against. +A **run record** is one execution of the checklist against one build: per-clause +verdicts + evidence pointers. It is **output, not source** — a transient assertion +about a specific build, not part of the version-controlled contract. Run records and +their evidence (screenshots, network traces) are therefore **git-ignored** and never +land in the repo (`.gitignore` here tracks only this README). The durable source is +the checklist itself under `../areas/`; a run is a snapshot that goes stale the moment +the build moves. -Each run record may carry an `evidenceDir` pointing at a committed folder of -screenshots and network traces (see the landed run below). +**Where results go instead:** the executing environment — a CI artifact, the runner's +own workspace, the sweep's tracking issue, or an external QA store. Keep them there; +do not commit them. -## Landed runs +## Record shape (write to `YYYY-MM-DD-<slug>.json`, kept out of git) -- **`2026-08-08-console-login-demo.json`** — first real execution: `platform-core.console-login` - driven in headless Chromium against an isolated showcase boot. Verdict **pass** (6 clauses + - wrong-password negative). Screenshots + network traces under `evidence/2026-08-08-console-login/`. - Notable: the runner's automation self-check (RUNNER rule 2) caught a false "dead shell" P0 — a - cookie-only clear left the console authed via its localStorage bearer token; a full credential - clear produced the real redirect-to-login. Also surfaced a run-time precondition (the vendored - `/_console` dist must be built separately) now noted in the env block. +The shape and the verdict rules are defined in [../RUNNER.md](../RUNNER.md); verdicts +are only meaningful next to the item `revision` they ran against. + +```jsonc +{ + "run": "2026-08-07-v17-release-sweep", + "date": "2026-08-07", + "scope": "since:v17 + P0", // the filter that selected items + "app": "showcase", + "env": { "framework": "<sha>", "objectuiPin": "<sha>", "port": 3456, "db": "file:/tmp/<run>/data.db" }, + "runner": "<agent/session identifier>", + "evidenceDir": "<local path — not committed>", + "results": [ + { + "id": "approvals.per-group-signoff", + "revision": 1, // ← the revision this verdict is valid for + "verdict": "pass", // derived: pass | partial | fail | blocked | not-run + "clauses": [ { "clause": 0, "verdict": "pass", "evidence": "…what was captured, where…" } ], + "issues": [], + "notes": "…" + } + ] +} +``` diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/after-reload.png b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/after-reload.png deleted file mode 100644 index aacfdbee179e00c28a735b68e006b4f9bbaa69b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73903 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 diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/app-shell.png b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/app-shell.png deleted file mode 100644 index ca781b3d4515b90f1f6b02dfa782f4c9136abea0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55350 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 z 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∈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 YTbh#&)~tL}2^3-QboFyt=akR{044--jQ{`u diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expired-session.png b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expired-session.png deleted file mode 100644 index a96aacc0c058b036b0978ed093e29dcaf4c70aa0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74142 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 diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-cookie-only.png b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-cookie-only.png deleted file mode 100644 index a2eb04cff7732b679d4b351f8fce683ee2147ed4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74520 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 yK> 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 diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-full-clear.png b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-full-clear.png deleted file mode 100644 index d82458c599b002f62cc87282e464b9d55771577a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44798 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 diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-probe.json b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-probe.json deleted file mode 100644 index 93e21fd1b4..0000000000 --- a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/expiry-probe.json +++ /dev/null @@ -1,132 +0,0 @@ -{ - "sessionModel": { - "cookieNames": [ - "better-auth.session_token(httpOnly)" - ], - "localStorageKeys": [ - { - "k": "objectui-favorites:u:aLSv0AYgrsEQK1sX0b8hL3tumDOafC4d", - "v": "[]" - }, - { - "k": "flow-palette-recents:u:aLSv0AYgrsEQK1sX0b8hL3tumDOafC4d", - "v": "[]" - }, - { - "k": "objectui-recent-items:u:aLSv0AYgrsEQK1sX0b8hL3tumDOafC4d", - "v": "[]" - }, - { - "k": "auth-active-organization-id", - "v": "org_msjw4dxvzx6tujcw" - }, - { - "k": "auth-session-token", - "v": "dxGIhmvvC0Wc7H1VVfRz61NOptkNsg0a" - }, - { - "k": "objectui-offline-queue", - "v": "[]" - } - ], - "sessionStorageKeys": [ - "objectui:metadata:app" - ], - "loggedInUrl": "http://localhost:3477/_console/home" - }, - "afterCookieOnlyClear": { - "consoleApiStatuses": [ - { - "url": "/api/v1/meta/object", - "status": 200 - }, - { - "url": "/api/v1/meta/view", - "status": 200 - }, - { - "url": "/api/v1/auth/get-session", - "status": 200 - }, - { - "url": "/api/v1/auth/get-session", - "status": 200 - }, - { - "url": "/api/v1/meta/object", - "status": 200 - }, - { - "url": "/api/v1/meta/view", - "status": 200 - }, - { - "url": "/api/v1/meta/app", - "status": 200 - }, - { - "url": "/api/v1/data/sys_user_preference?top=1&filter=%5B%22and%22%2C%5B%22user_id%22%2C%22%3D%22%2C%22aLSv0AYgrsEQK1sX0b8hL3tumDOafC4d%22%5D%2C%5B%22key%22%2C%22%3D%22%2C%22ui.flow.palette.recents%22%5D%5D", - "status": 200 - }, - { - "url": "/api/v1/data/sys_user_preference?top=1&filter=%5B%22and%22%2C%5B%22user_id%22%2C%22%3D%22%2C%22aLSv0AYgrsEQK1sX0b8hL3tumDOafC4d%22%5D%2C%5B%22key%22%2C%22%3D%22%2C%22ui.recent%22%5D%5D", - "status": 200 - }, - { - "url": "/api/v1/data/sys_user_preference?top=1&filter=%5B%22and%22%2C%5B%22user_id%22%2C%22%3D%22%2C%22aLSv0AYgrsEQK1sX0b8hL3tumDOafC4d%22%5D%2C%5B%22key%22%2C%22%3D%22%2C%22ui.favorites%22%5D%5D", - "status": 200 - } - ], - "finalUrl": "http://localhost:3477/_console/home", - "loginFormShown": false - }, - "afterFullClear": { - "consoleApiStatuses": [ - { - "url": "/api/v1/auth/get-session", - "status": 200 - } - ], - "finalUrl": "http://localhost:3477/_console/login?redirect=%2Fhome", - "loginFormShown": true - }, - "reAuth": { - "consoleApiStatuses": [ - { - "url": "/api/v1/meta/object", - "status": 200 - }, - { - "url": "/api/v1/meta/view", - "status": 200 - }, - { - "url": "/api/v1/meta/app", - "status": 200 - }, - { - "url": "/api/v1/data/sys_user_preference?top=1&filter=%5B%22and%22%2C%5B%22user_id%22%2C%22%3D%22%2C%22aLSv0AYgrsEQK1sX0b8hL3tumDOafC4d%22%5D%2C%5B%22key%22%2C%22%3D%22%2C%22ui.flow.palette.recents%22%5D%5D", - "status": 200 - }, - { - "url": "/api/v1/data/sys_user_preference?top=1&filter=%5B%22and%22%2C%5B%22user_id%22%2C%22%3D%22%2C%22aLSv0AYgrsEQK1sX0b8hL3tumDOafC4d%22%5D%2C%5B%22key%22%2C%22%3D%22%2C%22ui.recent%22%5D%5D", - "status": 200 - }, - { - "url": "/api/v1/data/sys_user_preference?top=1&filter=%5B%22and%22%2C%5B%22user_id%22%2C%22%3D%22%2C%22aLSv0AYgrsEQK1sX0b8hL3tumDOafC4d%22%5D%2C%5B%22key%22%2C%22%3D%22%2C%22ui.favorites%22%5D%5D", - "status": 200 - }, - { - "url": "/api/v1/meta/object", - "status": 200 - }, - { - "url": "/api/v1/meta/view", - "status": 200 - } - ], - "metaAppStatus": 200, - "finalUrl": "http://localhost:3477/_console/home", - "loginFormGone": true - } -} \ No newline at end of file diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/login-form.png b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/login-form.png deleted file mode 100644 index 7fc014c3c175dcb65e8b43fe0a67bb16298a998d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50149 zcmdqJXH*m2`!*UxP${C)LE#Y<q$^cA3W)SxL+>pFsnU_6(gmcK(0k}f=m<*hz4uP& z9YWw8p7;MdYyG~Qb<U^vtTSIGYceyt-TU6xeeLU>U}Z&VVgech5C}vp^GQ+_1i}M; z-Wj-m2RMn-%i@4Qk3ce#@72GgY|q{kCmU~UKd21X7xee@zhe@LM|e8tIg&8(gjZo$ zUTIgMVe>5KESXN>ZIYyEFpX((xm!7Zv0uL5KdJi2hDx?#IdUS#4j(Zny_<#WrD7X) z=cXr2kbnFipezt5e`n;jP!Nbx9E5*+uD*Nc&F%U3t4Dr7JwPCiuOOn^v%`bCMz?1_ zxqm>ow?Fjm;85M37oObzeR~GQ|NrV0BXpX!H4Ow(<tW)>po_F_8>e@+{a)+Zr9SWq zY{Mhfe8ZYz;Qx{c<j49P=#moChz;BF`=L5dCrW2%v+5lTqGDe;R*>TdXk;jBLtZfR z(1fDm_m7{Pd??-#(w?e9uxNZJU`ru+3v{DadPgtseGsTb;?$%xtT8dsq*rz{H=HG4 zuHe+xb9zvJK;>tk)cb#2$Ajs-*#f?Uy}az+PN+Nr#tcCPjDv%Ih4j2vz(kn=!lZ_U zb9}7mbr_1EXw`8?>ssUf;s}J^o_1-ofXXAQW>36G6HFC=0}^Ndc-tn+r$O_@YzMPM zC*`JHErO*^JL8}4u@yONA)6L?9N)9pPtS3g7q%v=0(OCTc3W0zx&E&0<KW;BXp^Sl zJ5Iueh^f+@Srdd|XwH{s=Z-t5{JJvHr+V`qdlF;##+r#=pHbrDg8X#9`L5uA#z{SQ z&W2vSJ(4>KA`J1D^9X7wXakQ-)fg$OT(TEFnEp=qT$YHEvX8m(N;@jfCb?#m1(+3Z z$=kZ}Z*OXqQqKFpWfUuTHITa7e+V6mbJ8<yY1186jPr^;-R1KZBlw2KvX4Sauk(I! zGvVTbl);YyQy#yE$!TJY(MD;yZ2f8x%`Q6y4}hj>PQmJ=9Hpn=Ux}QC&+TY^xHO9m zN`8jE_DTSno39U;wjcW2B-;=tvu3HXz?3lMh^yR)PwElG9buU9DBiY-pxq%<yIlP< zv%+)AyC9GaJurV31dX6qY)+dYV(K0e<!B%Hs}c;mk2h3V#MS{@QHo)wYI1leH5^i} zS8f*tdCwDw4@y7%*M>;AqAV<#UF|d|49-|f9A=?bxUGL8S?6vfnGH!qOY#ky!q7gU zwV}D}8<VvLX6DRn6e^JDIQjbLJ>7<l-x{mc4EsJ^VoQnGshHRt52IfKbGQ(u3j!%G zPqO}Qk7QL~T=epWHn%ptD#Z{JBj$t+26O7*)<>u8nw3({dpR3(atN20lX!agIw_&0 zc5#FAB6lD}BJAU1&ri<V7ER&D<eKs@XRFZ`!?jX@r=C~)U++pN563}tQQN#mBNNGW zLFm6?9M<ccmA+z8&m|p3@@4o@hpgB3t6HMi-_d7cDGfH`ofCm%0R#BFc2DQ3KIf%* z-y^0M&QB}_oQYtw6EIl3sUlb!qNRA42AIEy<;z@Y@#UPb;=9bfD;WEF-_*Bp5|uER zI#n<Vg6vC$H2&O37>mjz4?3#S_9T9UnuUzoK>F`;h~c7_;~L!81Ln7T*wDSXFPah# zE?4sX9bU8k_NKdJ*KtL7nY5Ocy0QpSGeIuRPm}!1rasS%JXR=tA&NfpeV2hL@-&Nw z-kc$O=+?<>SsM<yu=XCNJ=OhgaVOpxlUS$m#<^Z4z&U!S-ys53KO{3VYORqpckjn& z*cP8DYPq6~Y5&RE^RF5Ed)Jt~eo5=I=-}nzG#7s8XB2bYiMsPn#@u<{$?#dP;+a23 zE#Jggu5L<rC3~OlqSWtAA+c(wDSoN)H*X4k$NP+@vIC|ycLZ%|UA;E3!&Qw<^<rG| z>Z%U?pOsF@d?Q(k>qQq)B<o}mo5K}a{c*kiRhMCv2kj(#?-CdQ$6j*l(8qD8rFt!* z5>*BurNbdDwjMiY4yQ+I@ABp=A2?RGd=@FgruDCrw)(?*KjfR??~W=RblXLxyd+px zd!vNuMVPr+)b4y7AYV1aW(bs0;B}-o(_in;RVki&6o}d{I+^D7M#iSLuQwgHRvWw` z$o^JI+mwpkRAMzB8R4}PJKa2+tL-(|9^La@&E2JchU|r&Gl4B536T99KC^jHZ$8W6 z#p*zL@bq#nyXUMRb`<md9lwg=)J3-^eXl_S+RV-|cL1LcJ%JeaQyG?~N~1d-*vh;E zZJS~O2MJrFZQi*K4i%Kg8eW9O&_+(L4}H1Zu7RF8+w2$jf9e`gy~YFSNmoCh04;bZ z?;Fka^f<!KlB3PMx88Qgg^zK}l0$v7y;fw*8!lVZ&S<BUg)gD2b+CN%%h}q&%N1v} zp&(VQJCwcbSk?E27dYC!Uej0lr*{i>jyINLgak}7IIh+#(`ZSp_JZ{*SH!;^Ia~UA zI<}NCHC@2d2a=-7EY~DRcdt$*-OpNOb&njII8aT;!$@uLWh_x=`x)DplQd(JYsX@T zq_Af&rA@w-&VHYHQ@JLjimC}eXX-_3^hB|vmgtSThNeN@2|LlWCI5AmvuXYC>4sN8 zfbH*~J`hNq=GI3)E2EH9Gxd?`$0*1@g85i^A3g`>c&05$t0%0G?joM+rDP!Aqe#(L zS*3-xO07r*uFE7bui+FbD9rY~$mFI@bBO<|#4v&)g#Q&Q*OKWH!88f~2H9x(7lPy9 ztqE=0y)ZF%woZXLi#EFPx$_P5y!YWyc7!ONgsP9{YV1Y1xzs{7Ij87BFV3`TRqC#n z_XSe_`<O3Qk_0RmZg@OnYWQ?$x}*=gJ-9y3G~EuJaM=po!yf-(q8Al(HA0K)&x%5^ zT<AYw+UfZbWFw}|aE-%|%k3g;Q^BdQ;;@lj`k|H10QBAdu+yI@DR;nk@f|ixi|JLo zXslm(2ofijBDDgEE38|E=rtetxCo-XNm$R?wa1H}&^MSqgrrk(D-JfGfDVcEo`NSA z6Z&w$!cnbr@BhR?rdUNi=TqW61*GCVG|L(wE|dJtL$pLvXMsaoRZ~mQE$2p2E{!Q$ zBz5cVvz(n1UqjzBan_672^`|%fIXd`|41Al1(N$CAY<u&8KPa;bz+qVMxD2dI2Cry zS%)9GFKxn`!Y>&$srassyZh>Be#~ppl|{}vY%8(O(_2INL`+6_Fu%)6zV(U*p~*%H z)goIwh1%JRhq^GQ9+xLtCiEWGJCiLcT>W$tY=&}{jicvr<6;*ZC7M<Kz#_@{@1hkj zx+x<SG+9+P9j7&0#ZXbcF(UQWqTXat7@b5Or!<G+NMW*1ORv)YyJX1r;4|{z-b1yH z=hrw4yi)n2QaY#wj)7Mt)Fu1)+8@^1G8e1{rkApvs5MIaJJ<@tZM;MAdhk^uLW-c> z2F6o(4;*zRh7QOYUO)CbkvXs=1k=*PxQhp$s(cvgtj;ETZ1{z?Fg-#-@Ls5F;b9l% zqnLni_sDrdxUFXKT$N>eiO9Vt+c+01{_APB!=HC!RBl&9J$kD<#DLp+r+>zpqWj)b z?+MAg?Zqi;=yuwv-%<>9;a3Src&3<7`(dXi@|d-#5$02r#5e0oznKWrp{xFrkf^d< zYbd>Q7P6^zNF4f*lPJp4jqV+|p4nmTt3DH<T(NO@vf++Kg>D9X0}=oi_H?Oitc^V* z45f1)ucYryLAlHy#K_TYn85-TSNd>g2x&kLv$x>4goGeSxj@$73|;Lyel1oM7kV!* zsR=to5wO>2c+n|8x~~#q{DK^)Puiv54y-;AmY6qhtXn>!cUfzg;h?9*#*;)qcVnAn zJLO{6-g3YCD2v2m&v}MM*4J(^xs;QoC$JYkEacE7ZFd_-dZFiC{oBTUMfGL{TsbKn zeTT}nAl}(;F%-{&`xRMFYrlAm1*j0WBvFio$B>jvM7``%Cd)c$afoFt6)QhJN<4C7 z6k1<d$Q?9pyaRxlhYIPp>+6fq%gd0F(6J{7LkyzM<q8h<&Yz+*)jtn)S=~D{-xbsT z+%37fGi+DG&#uvPK2ove27Y5dpgiN4T&Uo=+C=vzSDy@kKNQlu+UMu7dd@PnnlT3T zp4a*P5E4>ZgNuou*W7Z#vRV@*v)-%MOa0<czJ>SCJ6Rqcmn#q#HDZjuVxJhoN@6^C zUh8+Cpx<q^<vNK9aY(kL<QaxP^z_zq0zXxlYjWT0A+dxKd0JfLNleWMmkd*uPK+&{ zUxDwzQax^}zxLiCE=R9HDb(gY-IdGw_}>lqye~B#k4|jjry|Q9#pksjE@okccFbH1 zv+jxAgg}Q&a6PMt&PKbQmnX+vYkon)@l!MJ00G$egMX1*`s<F&xw)~CP}w}R3#VaS za%|27s(lJ@6`s)1HR7=#r(RpFAH2Q^_(U4Iy<-U0)gJ)J4FTJ0xb8th|ANQBs%l+d zG|RlxL>rNLceJ!|DST4tZtTA;_T6imwD!DG?)r+5BYD>}>%8)v+kl2z(~$*!x(%fl zIh$U^A+~qN=9Kq3BWWNH8fVkIu)E<g5Pts%>}KafF_FzH3+10Xr5rZfV9t_ctER`! zEy%=vXtK`vX@?Bn4ed-7n0BWt9Df>)oP!~EyamP=VRnfkEcIKgu+Lu8{>#&{#io^D zdf3b_!doyvJbKjM^bde@C(T^O%v4|gW$kMtKL$VZC5$PXeCcY3u%9$KIZ|^*=TLEd zm`u-L*bUPd3si!YiQN6N<$N--S@>-+1WwCNe+9E{<;trQ5i&fpt$P)h=_2%)96nI} zAg96~IbhRd;mtHBSnd4-E>JG8u4)q@m~VZwAsUb;dJI#VZ!Gn-QsL?m;Jc}tH+3@8 z>c%u;R+?W!T(@%m5<$8}Ne!_}4e{Uj*v@&UepOwsCr(3pBSn%Q!d#u4IG@)uU=-=* zdH-3q+dinIw!qYTC7o<=AFp*r&Q0Z+=jZ8ed=Q!yF89S9u8Aqn<0Kd6VlLAOUE8U9 zNX?zCP^Oq40Cu%87IYKl<EtsFD6$pw+Uo7avNRpmUha5SksPTPj53G_yvnoUZ4o?8 zJf87{i4L<S%nt#*M<Qik@*Y6e0i9Mhe1r`K<(_#ar~E8MYCGe)TaCR)S*S#7_4F@& zkBMmX?v5sPnve^^1v+h(dKPeDS3i2aJ<pYgAZgl@F~{^FrCAHqwW_JAFU{<{c5{sl zQ-0|iqww;bX8ywk_+Z!bBv`!FEP)bruIvZ_wAn#csIL2h0dm&Nl_)D#dFF`KjDCC; zz~6f!@poccUF34&5Isg5XShZtdUry!$uSo}9G}zI)(Z*6)Mb{Ii5y$4cTw9aQaIcG z5#n2Ws0PIJgg}rlu3%aBbljEDYH{b4ZOs@vt(5!tF2(HhB;S=+=J`2fxPFpAXHP+p z^CpA~b_Vv+o!;kYFyGOdux;{{!&fvoSx_+b5d=?+JM53(C{!0yfc$K~w*P|zD#67g zH_G@-DuU7FN$Q5jx2}$Lw^;wsXw${~JPIBoTrWAUpZ7=m>?~W2j9tI5)4I714~KCQ zZoNB%D;d(zG6J*&;y3SYNajbBM-lplM#?qLWRsya*QJZAbQZA+4ux-4sG)XB+4lv3 zh>J>ft}5Uoi~ZBQ{X?V;5BWR}A<#tM{96}}drDAN+-2jExhHP>$?Cw?4c@YKY-Ut6 zJevWJ>+UDuz}6eCPnA9;o0LDMEg7Jw@@2%{0V!kt1<}Nba<enVJ}!U%3bFaPa4^l? zK^A!tJ}G)}MxjSi{Mzm@dy1}t5Kx0l*6*R|9wtNHvh;@23@iRXAiE=R8_3c>MYRms zz?)D0Lfo`n1Z{yJQO@vzUJcr3^&cqot$8iin+`swlX*7`D+siYTB0!m{XY47EpHU? zJ}RCyI)?mc{l_p9%ja3BKtBc%InN+F<5<U1mNq&&OW}7DHEoCk@@vBR-@&@J9rZgg z!;F5?L>-eGj@BeGNsEuoo|;8RxL@H??6A<T<{+qm?4LPNlY!Uf&RXE_b&A#2aM_%l zEknT8SpN;4sf!T%xdRs$NUD@%RxS=^B=iY*9Bgi09gQ#~A(4s~%>;|(7(SbXuwDt; zW8^O=L4H#q0C7~!G06?MlEZL<fcG0GhdJ-u<)4aJDTs19R!C~*^3E$eb6OQSIFY8! zwRFqf9i?QSVo91wAE*Jl2vU0!?s>FAU^t-slYbFSY!;avA7Pe?F%Lmj@dMhR&X;T! z#>m%!Ic1}j>=P_Ovep`ffx7H7JRg=B%{sNRYMy$_G=V@P9yT~Y=XIv(hSEK@i!C23 zjxpAL{-WG#66WJ90HO@Hrq_8Tlv3E34K!Ep-#+tZr3irIp_(fQq>tJ8a$kov_i^6F zrnl9Y`kiJ0Z&fRGf(94M9piz1<K1=;;vO?40KNVGPjh9}RF&_eTvVgn^{7;X-b!m9 z;(@Bka*_wW=pe3J@(M=l%9X8wOGN*=E<e^cYp*%%DRr0vJ-v%KK3RE@(I^%9&*R`! zQv{8g#jZ=3=Dv9un&O0{(nEy~d?5fi-~j%CqgnGrLC^XGVtfJtrlb@ZbvqfgH2d>_ zKqXS3Cjg2_d?)YI9K+D5_ST73^#uV){PWlq(P|KaAw7V%tO#I&6qrHju5x0<^0TJI z)E_SK{J1)Yvk=!ttF5yka`*5+BQ`)e2wm5d!x<JQ2WZCWBaq*rm2hu!1KGM(=r;Eq zkPYXZH-1y{iOBJ-VZDtzcR}eUwY4p0_~uihKpiM=>tNnfzLtg<vt|Z?VvSC0nH`@5 zz6ArUmlOzJ*_OFN@qX!>=70!Lx??c`Vze@Y0i6xh3^3aC)2??cGZMfn4~0LFB{JOw zji}yMC+-8xWYl5Z5|n?Z2>dL{&EWQ3-QplbeCtbfUBFC*W0QAQ9)J*J|CY{67$ge< zu_$FAQ-yKT;}`(1104Uwnkt7KUeUO0Q-<_tPeM+tEo@wya;`xDW#fmYcl(O(*Zpf< zvlfz?D?gj_#-b~JyX+(l473L1fPDeN{cT=x3hw-RQ+%92Qj36zE(gBX(;7k<#1|0* zG!!uaX7qA91a&&Un#<@5dRJ=&l$>gbcfTEcLGNiZ*Zi`<;rZoJB@;=c4<lnkF~5sw z@027zabt+In#MaYPy^XppOgPl7t;NfUPS1%p4qF^@ab}wX=uM<a??t=p;+ofo0j;v z&EI%#X@I!&^Sw`Q1gh@Ynyrq;?an$P-Wy@B)oKzo&6~MCwS+-*4z-UGsFnI75b<A! zN#Gtfhrc@7^c!eM_V}^u;hp%;iQ0X>uvPTi7DYch`jjf>Ex$5i{uyaa)P8;LytMEZ zry_gCr|a+dczdjhQs0!=9^BX)zpKK@YJBqy>+q&i^qu?Y-;zI2k2QEpXPu(2vq5o# ze^TQKeB*R@9<vybd`U!92JbA^c<?Pd!8}qi?a|a1qvI8lI0}U77!!TULC^6<Ttfp5 zk)6OUV)CsHUH~!15cWjiFn}6%od?FuIf82y5WUTsxV|P^SoDyIr4rPLH9*PzZLYSh zWgg?eYHx;745hE&=aoQZ&*$Fzwns`Stft7$dzMYtFk|m;_a=}YThF*nQji`!f0Zaj z1?}annje0)LqPBpi#==#y>B8n`LV+n-wF7neSJp5ZjRBqWw;o5Uz-E!t)Bb~(euTA z;yR^wBsxv#zn+z2z^n=a{E<Uq!6Y{JskV$yI7WVJ4Y?c%;le?aV7<^Q{U%_=E1%Yi z7}jecnYyjU0&YGFt3P$bHZj!PVvGofq`Q;sf{`H)$EhQFJ&k)R-!`kIO+Be(vsn9E zCPJ_1<?gLo{)+0f|BLYiDH^eCC`4Adv2JZZ3riI}7!C+}-1igdA!9cY0DTs5aWQUf zP?YAqeeJ%!^FSB&<Y+UjdjBBO_xSK1{hlzPJW-!@S~S8N>WiLy-~4?|^s?>SI|0>? zJ)vr_OST*|Yvp=c*u{3%^KVLdP5YY}E%M3rmXrP~_RyP(jR8&)SH*;r4ch5w$=@Ak zmOg{yO_MAAeQC3C-lyNSvRG|)^~RnCiW;BDLG1=2d_B4|G%Qhl=k~hJWyr9}EvZJ) z{AnjxuQ2t|QNL1^V%ou&S|}mE0##pW5+y0$37q|{m@j`TWV0LE%cgBPeNBHdWwY~x zQ;SWg?j%49Gd4brpU2^ST8>$>Zf`tDocY=f+o8c+4@Xk$yh?Ag8D?|7E^M%MeOh@| zGD%lwbTa4KOM4#}p4<2}Ugrb;x?8eJYUKMN_aOd#itkI&T$NmF>_TH^U588`dB!{W z@8pVjV%k(-y%C8MMd0HJt3PPDhm6EKq->HiV;Vw|MW`$^q4zda>?sl5*(q2}cRKez z&A|+5IhNiK`8K0py#4Cra}h=RkH*LbUIe&WF;L0#YBZtXtQYKJ<cKyvqGprAJ@;z{ zd#?iTr(K;pp>rFnUou*c+y^_^LC!n)4Kex2s7JU$!OK`f#qcET*fd@PL~=&q=wX{B z(GW!c6fBF@607qzlMr~1eIG*~2Ilu(TUu5F^TuKu!!g1K3qK6rS2!&kA9Tm{g$8$T zsO6wNyHg~Rd(^43+drZR&PEC5$6nAPIVO5kF}$xmrm9+Y4VLqo92wU73{9fHh22LH z?{5j4PX$Q4SWzUT=QkdxN4e3-lEUj<`@mH%9RBumDm^s=n~0YlJZ`Xa_nhp3o2<}A zicIuR^KA}1C{5E7JBUB|oW)4tB`;3Qn=Z$lQO055Dek(yo*WMq6t49h@$2gaT>1yV zlfVAhHhX1zuwW?RI@5X737+2_Sb^ka7~rZMp4478d_+({i=#VT?G`RH<Q{I-ip<Jc z^sO9W`g3=#>~v@QQ*sB8vR%$LS}E>6z9Hb`!3sUQz0&ytsO>y_J%(#F;c#`7t+o+{ zeU|E58He-DrCbId3>v!_*(2MqfMpp5TsDdtq|$zw_6)$qx+E5C;!6y5avxZd%ud45 zsfyhsgz~+Qkf7p|2PCJ=dQAH1@qIs2jbCe9<-`iy(+?=&=Sw*`uC^}vCV3WOKU)Lc zGa&9xDiC#PkJj1ipC~)nzaYZ=JS_-%rn{GpKUbT%YXpBT$}Lw)`I)KGD_IitfJHR9 zvi!huSyPFcXV~3pd8`xvJ?JAbf4Y-Xxa=$R?+VZDbk_zUp2z=tXX`g2yFWrG`PH32 z0k~4pW9(KPguZ=MTFjvjBIyQYyx#euFTM_feT7zL(b%zOWV5{>T&pzRk}Zisg&{Pj zoosJBZljO039mGHIAO{}EjA)i7Y+@0JkRcKtgIZ&Pb9OHbO1SB38NP*1vlBCn@73l zJT_N#)GWQuRZ!8a4tA%6q?TVqF4}BMs|ZL)9H(zCtKNb+oAmA6tV8i*srwLI-}${w zPAyA6dIA7ndHORku=Yu8GL837Ce6EBjavwoT7=_rJ%r4{l*hghgClnsUD<S+KDTaR zMF(TDaP4rtw@}uvBjBOIMGOAw0*dKBLW5b^d_s!P;h(?#-godcAN~1^)~Rvd!^m5A zdr=+o=f^_VT%<1TOm!o)t677oQgU@OurIFYW-7+V8+}G2axlt&Q|Hxc36+A)S2J)l ztk^-Ob3*CfA`Q`N@8<+-teMnKw6t>}H?)d>x0N{fMmWn{s!y-l;Ggx5L(zRn&)Ur{ z+`g2?9j<zbyaA=(0jxG(@iMGB6>Q2!QLovHS?DW(Jqt}c9b2&wT%5J&+Tjl&RuaQ( z4ka9U=tzoGxwPepLD_mtaZ;tFFs(fv9v|%-=HIKFh82cV33>mVAM2;{G@XnHWNQ#b zPmG!G^}hCbVWnSLK3p3@4r@4<kk>oBcy=V}X(OYV$80J{o;!0j@e0=1_>PbHqNi<= zzP@qPb7iGuF?KF)!CozGBTn!raa<J3%_jq`dvQEOT^VA3SD&tQ0pDPgX?dxXPkBqJ z*aO;D7>?%_s3_*{VCsM0ekq4l09?Aiad}BP?b<PTysp)7GZxA^RPw8)VLA*p!Rj<Q z%z0D4C23PT^ZbZn3YlX(&eRsv?sGYoCHD7ecUdoFc?mWTekw=zkr-uz9&EKGnHk}< zv5Jg4<gGQNovgF)ECx{d4*->qb{M^@-?(NT{vKZ0uzST*GD!-9q^g`hGSdI^p1#)y zQp~ru*=f4PM>Pn&v?P4EDwInUK{v?yhFvu0I(<+9`xF-=g0yO@O8-;YcKl2oH5v#u z)UD>OSz-7iErvbn9z5VqvmqC;=)sgj(!4R<%u))uh!bea7}N5wb$Axj9M22S0ou}r zAx?4iG!B!gJec<+zwc?~Fb)6a)Rz(PUTL*S&Pd(x`P)=d(e1hHu=am6RIipuzPX^6 zN7HQsC?h&r|HB15hqbzRUco;jVI(2_npKHcBhudeO+5DUX2-wsls<~_*H{n4a)^!b zQXjN#Q{r22)ImFb;1801Z)nO$x7(e)2u4wgObhz7`PQPgNCzO7YTU?HA%3!ZHrXQ5 z{c~Jp`ZpZCI}NzCJaz8k)6eep-L7V2H8sBQ3>wggdKW-)GeF5XSOLhT0=sAwUItd& z2Mr*-w8IglxG^OHP=5IB9t&{%w~PRxK9CMW5kCR`c>MtggMs7!f!;uw00{fzQqO1C z<;28(zPJ`t>&vZ?Gc<@xU@la`JK`}fNk*Knim5tEjjp6kT^!y+^P49~g-3fEp8`+J z#aI`&@CejVvU$6I{$Ozurs-W`0xoH7#s&R$2_kp{QipAi>?A)d6-tXvFjevNkPgH{ z2HVWN7*fk}B_uxVK2i^EQiLCp>=T}hXsmBeyQfV)F!Zg{BHHj(lIv&bdCyqnUZjO_ zb+?feA^DoLrTbJEfcaYI(vLue!OY&X(F%lWSPy4Ap*O#W^&x$-vNA)Izr<a>2G~C! z8<iM$G%AhD^w1A=&%|E}zz0<e0>E9J#Ps9V^~=F*C?<ItW3?T?AVE0at$4NjJ)u4g zOaCL<T*FCq`)iG@VroZOHRhgM#L%<J*g12roA=F2JzJwN7{SvqHp}6(Y_kBye3g~_ z4EE`Bic;EcU9>-<s38+Z^>`~LYX73RA#<5{u066Xh5NqJ{6^X!KZ@kFK|rtAbvtZ6 zM@p2d(YkCZrOF8lwjD~2pyFchQZtn`JXyX>_?f13JPJYeJNemMkW53^5%FxkX2Flr zh<9PCkK&0olPUM~DU+jK2`5P+Qr^5_sGew|yPOTx>JQ~7DBn!Ptr)NC|K;<??oY3E z`90A&ayN!Q8+2q`4o1i6Z&JNUAMVYdoX7FNsWanEa{U`oDn75!ZMiAf<Wsm{^pkjF z+eP$z{!Z1P0__G%!dQy)`R^8m2kpZ|jBx4DeB?6b8$MaFnf*{=5bHy-ZTdY5mBl{7 zC@8zW(>q2{ex;lTA$Fq1pN4Y{@H~ZtTxOFJG>|)^-%xf_Bg%bEajTJ<V<|y17khh$ z2|PKrw44}iz<i2REv+s_&f9SMj`|fRt9H&$myr-H&r9USg#8DUIK9ds^49uJmx?#i zd@Nm41)?$tAT$DwMo0LQ59G$^LxaaLfl^^lhmaC}g<JPXKp+G$z|RzZ|5$XSLL1qh zmm_s?H{RE=R~WH()M{CuB`7k=t{lU1)U#;TfLflduL_(MfHTd|?UX9%)UsLZ+BE$D z(_E^TsmCmK%n`o0=j<yg8B=B3e}*>k^1lotaB-sf!~Sjv{8ULGd6;KH%a>2I{Uw{F zQqFqk>$DgT1_{!sVYL?_B_pGw$!~Jxa-B%6)GR94+v&oCiJtMi8T!Iue=?U)(|k?x z?t@RW*|U!%J*;zg1<!uHp&8=ad%~_;m9~<M@SQHm>3$ffxH+O4U+Fn{=`Q1g`~5hP zBC|V0tkDb`Cy<-wMv<H`BX^I2?#(IH;Ck94z_`1DojznCbLJg$Pvoa7SC?HbDZGV5 z9&fIveKtO+P3YIsrlTHIZmV}ca@`sKrc}$y((<d?d9pcyXz*|2w_s^8n42CcIWOc> zgHaS+rS-hq(XrdySV@7P({P0XVZZwswa6Fcvgd1&sd7uiyX>-i7mm`b(7wk}iy6re z+f#;7))tuO;`ZYaP7Ik)5!jM+=%5{+J3dKW4Fp%|XmXh;4m!m*UUZXscrWx2)yfwO zffi*>AyTvZCaQKO!fKc^1Qh~ud!hEfklxb;?nT%a_QCh_SDLew5-~e%SY!RPH~}31 ziWE7JvVrm$4NktDKo;og&^L4XSBF|fq6S_@o|%m!#39F)k$N4+;q^JtKlqKc3mv)q zZpTg$Vm9*&#@;mi#QQ||A@AOF_5VtW&My5@(6t|-;+>3l_sd1q_C$7&A}qm<tz7xc z&xra@MD`kOWo1Nmp{^vJt*YA4@mhWMua}Fn4B|{BlFz*Uq-JGdkWgW(PwmFWh1uuD z!YU6~BJ-cm?rl$3%VAt&zVRH7=P|7%gi}-UwTHgeAx6S0EhW42RoHnfAEwB03n*Fl zb-$%T%sqbYBPxjUTNvQjkywQitST1uPc<_2im-Tmz<4;8;&=F4_Iu>2Sw@&8sFC(R z9rQe-nnFsHar{$t(CZ!0KsfB<v-Guk#gCTpINXXR{pdmdxSM$Qj})im<&(Cs?45(P z{gT0~q>J-&{8Or_iUMR<IIC}!<9hJ{obs88fX_cU3v}vhJ!+pVCB@g@C@CBWbPry- z+RkuI3`_gu?ttfqT6TKA5+*XLTqEC|y2f|T(ztDoxH@Y2M&&{WP~Cy{;hwIcX#=BL zX4&NPe_U6e3fm|+d=d{7izmPZ**w2?!S&n9B#79$gi%}+6j(DUzWV!AfP4M>a?zBF zubEXtw>giRkM6fYk$-+Sy>Th555Rhj6hxfy>MFr9Q8NuZHcmci1uf=<yrOBfEq8Oq z+>6)>az3Ei_|t>(-hRy0gve%2mO|AX<h^DNh)gBDz76tMG{^3_<QzWOKSka+Ww;)l zjsCD}7<69s73Z<(zdzriG5qDyJ+z7^VA87NQ+KDx+db5?zN}094!?DEKCO%Iy!Q=I z>qpRGFwbG+efW5;b%Rmro3&ru5JgSZZ*SelVM@C!ryp~PE;>E3;(ZBkTB56!K3N4? zMzc30hsjtAxUZjL=j$*jj4FJco&Ce+<^>{Ie}s90Y<D?*-@XIepu+@K!!)l`!fjv_ z?AZ9ym`>sBP|Lok7n@sU8@Y`+7LXx&F^4Y~eSf*FzqOFei}5R2%pM|hR^~KoFdo&m z8jpj**Ud{6Ap`5N*BxC#Uq*bqrO!42YCHdy!%&8gRG8=}HP$+Ji#1;M-#mNglMQa* zi052}N6@~f%wzUp;C#VZB*O5)xoBA}!gl!3OM=1w4wpz|>JOz(p}eyP2WMLG0t%lC zyNmP@zI<Iz!RXobI3HACjmDc)#`FVRH_UcqgTs>E)afWwTnS;KGdMo~Ft=B6rdacv z+s@4={-&i-^`l9(4{CEZ9cB}+FAKm7cS4$yX*mg!qy_kD{3DC&2fNZLUlt8NHi635 zi?Y15_KeQm{P9~A64q<!W}jFt$L;Xx>_PL~bz|Y*(IT-({$%+NaJUhefXr4dy||~@ zbv57~z#a!N4uBlO^S!)yZDQUk?TsjEz@x}VgmqqU*iG00;Gn;Lt^AKGb(tWq@llk# zTjCcNIt2+H8R@>KGNXYX<LrD{Z}iK8G7H5TwX!O(0gkN-gXfp`LQ_cdDjZ9NEsVY^ zs*rv>bHCg@Rgf>h#2f?j^N8q#yfaTt7I7F$|20n+7OQvsZfPcLqy^(x#7pe&+_vIm z=_qSo*fu<FxWY)`{ow=5g)u8kdM8du_reKzK)n5gvG3`pcaHx&*P)yc$acm!nGV%F zb@?P2Z-LX?XipO(%8W>#GiXNM(uM#tDSq<C;$@Hi)5_ibpg(zbrC860J+pBZhbNJ* zU1=>*KU|3exR)r3<=!P{C<(fJSwvPfWo%p25l#I1%%DU5cn$v8{@Qr1%}FNM+yKP( zCp+&IGxJ#p4uT3?=GHBz&|lv|-pw`kjVSC;ajM34u8&Y=(jrTA{rqc|+{0{2!xRDD z(~W2mN`ew!zP#3MGxXYC4zrss!Z5ysc$SpJ7j?OE5ayM|EVen)`3g)LE#Jb#RuOJ_ zS;+f6vipGhNqlLr97j)>eMV{$>d`s{hftZ&Tc&ZdK6zO^)b7S2Q6*!#LFA{^0|m_3 ztF7?M;~n%_tJyEa^VJB=-IN^+f2LHr=Tdo)=g=%IZ`eMIX<PI2wA_`y+>Tyqeb+A2 zlxciZkv|VnIkkyH<Lm}ECu1Xp&j4;%kK~pw8vWx+Zl%2y(SXr>WnrEO33mZYB78G# zB?EgBCgY`#?nB(yt&V1Rh34mMAE$j4$^3A2w!ynJHFS<IdG`k?wU2_oUHJFe05tDF znh2e8X&9m3=-l8=06!=AriUZ1{U7~Auq3AAhdXBOdOv;G^k>)Y`e{INPN!XAi@AZn zC4lpFj5B4bHK=<2b9SB^P^2|cC!%}2$&;Tx>Eu4_!4v9pPO%q+e%OERrIaR{`#Ex< zgVye*#eI}kKya#h>33LvB<*M|g-Vo)faM{<natnpY4vU`dV}-jV7AJk>G5H)*JcUu za+KwFoY~5;zr~jaXRkhx&#+ZUPbA`++kL9ZiWRe@h5h`&Xkh7P_T|`m({POYyE?I; z%TrjT<zn&tT0)@br}MJc%ZJ(iZe8<nHWtwu8zbB{BJ7y}^&iP_t8Gr_Q>F9jf8t)0 zKiDJV$=A@Nz-=vi#%BO*9NbwgfU&`0WFTc)6&!rOrg-M{{WK@{=YYII-MT%JT|0hj zu2}BwCa|BgFm=nb&-ecFu;@$j#@mrYEUG~Ai0_{~zKvYg0N1sDrs|2L7TmyQ+{&nZ zxdHGF9RD5l02lsCJS~oROBgP*XiP49@ua$qtcAhQ#<oP3s)%RN<?OBgNgSZkcyb<s z510bm%ptibMFu1GT4cp1&J(=rRI|=1HhOaYR)6;PwxRR5+uO1oD9@RG)vcc}6E<F8 z(SHPDX)Zi|zr_drHUw0&ep@Pt5R=nQ`X;?&%jkm4&&M*|mVHUoPMfm52E+$%eSHL+ zcI&v;`>&Sw1-u1J6DwSMVVKoE$F(>gyR~+w_?I-2v5C>9nqEyuL-WIY{5$(0{7o|s z73X|$sC6ebQYEVT>A)=(Zbu#YbtH$!**K|AW>=9qqv>Vvo>=k;c2TeWYFKgi;<_6O z^I4weU$+<dWy9jTpivA2O0RLpZyiSdwL>Q1hU4<1m^@T&W|X}3cNAMB2^C*~VM3pS z{d(;x7;BHFR9*d$q4g&UbvHi%k=fM~A6<EjV{w4me~9*)R@nM&5{0Z6fhvloMX%FO z{#DZmjw*7GmF3Qb88q6-*&(f5-8(vSiuy-3-G+TXjI6s}*K9=o`ARx9()mNbO7OYa zg2HmZ=z);d^gaB7($Owl(1Oh^!_J;ip-87)%g!hI=Vg^k9ntnE=ZwU|-jHeTy+~Z< zmMld-)F9hd_u*zAdF8`o7lF-5oX6}MotWrZK_F&g(|d}6?DF3`3{;kSShKpjA&4Ym z3vF{c+hS-?Q+)MmJzpZTHga!#KnQbHi<>DSy@SMaq9kY%%q?a+2|nNcxwaKrrwP@g zgt1c6j}?q3C^jSUPzNLb;Q~I{@vXkvO(l2Y;#1IQl8Qf&0Ds$%99tyc;}BI-ZaPMP z#wcWcAml2W<Ga}UqEj^T&1Hv|vDU~Do1<Se&MuZxtc8*K=t4DI8r&pkyLWXV_N<@M zq}f-KaFO~mW>!Miom|ntWV3BMP@(8-TdtSV2KCco=evu|hW?z`d&k4@Y5r8%@#Ik# zhUswX2|Y$})2(YxOM8W%@263%4Q^-lUOB&+#l9{dc-&BY6(zrt&>Hg(+#H${8@u}4 zReW?pFtkcoBUVAW);sGX!xXJ1bwq&^AHif6<XnSoTXW&+($1-`W;ZP9gV5Q$qliy3 zKeZJqn=iB2)xnHotJ`O))k@QHpLNDl?v2%h<29pPR($T;YHCp?r({yP1bP<P>re5O zyg1X=t~3(Zk2bjL{s6$`?C0#%pb{MKLs$`snbgBC$8+T<sWtm~XNJj__tG#<C&n(Q zD5W`mM>8vXS!9EaP2UiPm}d6v8|LSA=JN+p7wsl$M}f$Ta8%RD-gKKJ1xfI?<1Z3N z)4K`BrGvzk5!(X4?7QR?s#j?31RdpPt<QIMG?*Fk;ZPr0#lCVE^jM<qocCTf6BM03 z*JQsb<5@X<_3Ncm5iOIjw`aWIx+^>cf!H~*7g&k4-19^)E<W>r8ms!J+{IRNou?5y zC(}POC*@|;EeIaA`qLS1JGLqr1`}LOl-Ze4g6LG;M5}y3(F(chsWo+cMO|t2twi}B z+a}i^4=FI;bKMz6G(oo}+8Ix5;=F4mmkpS5t0s23mE4Jv-n>`b^|sloeK*}@duK}c zc*L7?ek{{xwQ!_u<QG;T4bmdqT?&h@`3Ee8jU4&;`AqSPgR`8Aji$jOM<+SEM>y12 z)U^*f!Tt>WveS@Seia_g{9;p8d+**}@*70VyZRH!5Sg8Miv<ZcBI>c8{)^?2bKb2f z#%!Y>o2p*XbF81@qIhu})3Im%PIag>K0N8bpFY6Ws;Igj&H~LqP+0ArtEZ(=0_;-V zBTi<d^HT`_=nl07>pDKtimqb4%=_-1--5Uy2~>&X0;TAe{E6$Ar+Hb|i(;vt>|A&w zB!i6lU2n~E)@p5MmCQ$Wo~-SpZD=yRd-o~%5zPIqz~IwK{uaZCP0FF?@`#NuHU`x& zHUWIT(5**X@W<hvAL+E6IYdgs-KfKJP72zeNSVzuK6i91O-DXo%lWQ`!T;fX;F!jq ze6OQw{0fOlim%(b9FfS0pQ~=vg%2^MivHRdXQ@)Gd*9HVs@GBdZYhWlFE!Wijp1-P zQKd?ol*+5Ms&gluwC4(U)HU>!uP@XlYs(^xxk9%-2ugauDhy|ePVN()mYBMp^&OlZ zj(DJZE)O~5HPKs0;!0k<Qc_hdlo)HjbC9py(Gxe$*%wn0Rt6&@lMaWG7lu2Xni&2% z$cI1_upo_o131y~54)YI;J7{cPaPeolMCceUwKCAAoV2wqmqw1YjBnx3odGkYlKTa zOA5S8A42~g;+Y<qlGj{J;qI>6Bxs|S*1S!(0Pmv^l$v`LQy5WV^@KB)x%`cKw>?HY zT=NxIm&_ESgsb9Zqr2EP2Ti(dk}cN*?5ewF$FCwz)6K2)Q|vcJxHb>E-B8`2cgl@= zvQ$P{;N~v1ni89S14dcT<9)au`DDw>!meLVSLzXN+C~mRH(jyi|HO1~Cyvy%G^~`k zs8QZELMU(ARaSU^O!99q98_ppQIGfR$tBPd<NVxT-1^`v?sp<0w~;7B_;<xi=y<mG zG?GofUV=ZvbGd8V74QbPnN9gVZP}*XC<|UX?`es}srndSpV~e?Ww@(WD4Xf~r(8*g zl|5^hOPp%m9TrO3YjVLlTgn;+wOzA+)bjY&$>-uHQsN<+p4K~gnEFiD@s9yI8&&2v zwCQ6W?)c8sB8O`ZdASXjPKrll?gcXO>ORkX%z33XJ%2SgBi!m}+mUsH6hf4}^A)fE zN#CDZi*_{Sw~rik_1X_!yIvZ~t!bhs)v)N15%`&FJyp4c`5ue-2XPhr9x_C^n<FgY zGy2DEHE~FB_uPE8we0zy@4TmFBwez?uCHIDL%M5seVU(nB12Ovh4@AX-W{{pD+1JO z*R7CB87qIu)-Wc-CeA9a(;ku1&6QvtG4y4O_zC4%t0i^N;#CysI?KUA3hR)y#>bek z*`F@VCo_#s6*MX2(EIm~NUX>U1zi^8lVqrdNnP7RJ92UG6jG1W6jQ_cDHw#@-BOs} z^ptKVOQG-R1_i4LDNU7tIbNGXL%L&er@slTF_UhGtR7t8X(%UBDd@=PINF9?&aept z_mvjs&??$vkNW-PqIlEhz8dJJ*1q;e$He)Ht;dMP5R@W$6Dx^YShdG0qzr}<C1fsg z3+Igmx!*F&&`vP^hG#}~8T<qr<$I15#H^p-vHG&0r$pvD6KLwJT5B$GzrPzQ&F0Io zpRV>cjBcig>`xV7x@z&dl?$oUvfTE|44?}<!FbQ{JE6}n5j$k$)J>j)(DvQNg3tX{ zdz|subC`MIHx>35A~9Xbda&x%Zk>Co+ah^uSj(V&o44oXUvs3@J@4JKHaTj!8Xkx6 zqISqWZ>c+KV;(*igl|lr4bh_;FKyzLt^Wv~JY#+l$1Hb0vh2;-go_+SYg!Z8z;0v5 zAqVFu-|i@CZ!5ggf^O`h26K;hlh$0$D%_KJqt0WN-w@K@#W!v}X70u9={@?QLQQ>v z9(|2zlg%Ke5j6hNq#U`H6VD;39r|NJf!5MqPAonlCwSY(lUv$-%x+}eoHoRyWU4_2 z^HZS-dp+k9`FlT3KeL_^qK6XBck4jDYD(!sMIMgclMcL3<#DEf?Xq7g7IPBTe>TLE zzZCb!oYU$&pF*YARMY3O8E0`iU0U1Ev*y)8vA&ZpdxNZgHw`c3rXn&fmHn#q#r5|* z$|8pEGdx3H+v*=}wj<{u98ZgkLR+SZL-h`p`RCc6u$2D%;lu9=ZB-}(m;up{8NC1? zV*cd5FCw=BU)qb#yZ`9MZ$`qmcrF(rma8S<`+0MqZ=$~A;j@6E>tzc7;;wJQeTO@I zn#Wg3vUbNtx<w{~=dljTBH8mR9b7EGZwA`cAt4ZE2gqwBteG_y5d2L20JZ@1#)Wl> zx6Sn$4w4h=?nvOaBq6qSbMD%5nI)<VMv>PYiPsCJhe9Fe(#=+yyqgnT41W~>CrT!L zi>@;sQJ}>fP4Qe(T_vOuq*{Lxye$@sUA{X7_K9Wg4MdD@&VE}>bK4y~A45+TWJbZq zcd(qYc7Tp2pXHY8k3_%KvA#;0KltSIIkiZY?Ij=-TIi}K<N>w;5WGqYFy$N(UwvQs zr=}p#mDH`PqGO5Lv*=YPAT_UT`aaX|2<`UfIP+S~tECgmEt&?>NtI~;k@tuJL2Ox4 z;L@JZU_qdYdGDkA$@2t1g4Zk7YOTY0I3v2ik~(5wLt?xn0|hDLX~xXC9^U;Q0hA+& z=8EAHkY5*f_Jf@hY_%OI9|VY+;N~Y*{~Y!ag2Z!}k2tg&82%%Tig$Zqu2^42g9l3I zV#ALjRRSbdp@1%KL{}5uG%6zbc@r2>b$>2w***cKJ)lFkVZN1b_2@Ri7>=q349P%# zA)9KFYY*hG*Z<LW^=RHLnKw+VS4nULUMrldr!mB6i~agxnH@-$1JqRctHmz5VC*z& ze<@iT8xH7oYZ5F&7}fG*U`{tL_EuyTmE0}Cg;VX<Lt1!L#4g$kZ|nv1de4{hxt0@n zoy!0*95DV7Afn$Iug&nzXKaG5{YSIDYEZu95hV!E)5rj_gj9<^8uw^WaR)uuiAptc zDdqftuWNll^qUvZfmSD^+zs+t)qixc+4Ac}&&3^(UPzZhzDQ6qY_uT_P?Y`T1UXOx z0|p#^a_Kwtw?F>xL&MuU{!1Ztd*T0lUaj6QAP4ieIWrm1#78((ezxW729=X`<=O<b za6G^UJh+Zo?3%~pyfs*IWOJUpGzM`1_~N(5gjz|bS@fb-fqHdK!K+wVA6oT8EQufM z&%F1c;iFbOo$<oYx~SOt1O2Ipew)8`bRq@@U+4nSZx(Hckw2_o-z*rmQU?l6?)fbs zaOjz?krUm7f2j7)zkQ;N={#nYdu9<c`A4E?xaI*!JgQz;!oZ+WH%levD^4{iNgObe zZG>S_>(cUahNjv%o}bCgEQ0IG!hBT+&O+_q`5R=zp92_yT&`hds|jCnef<ou1}HPS zdmNv-qR3|GekR|!lRpbYfi~<Hyox?i+|<1W27*JJAa(Jymm(qvi<r(XwWa^jD<)2w zjajMF5XXChev?<%SgW(d3PXU+(1zRRnWl$Di#209w|D#WOF`+X!oo9lJL&F%f__t> zfaY+lLc7=op_;QKc@GquS)tMulXt5|+zSGpyg2DK;j60|Jf{W~if5^S*({y_ljl~c zNKr9+-F14>%VJ9nh?A{>7m)FM%_ZmY(+!@w@2A^sqwzXcc<>4ESb_QzZ=4a6+c~d@ zw@OHOyY(cQQI^Vg+Xt+`l4n!kzhZmCcn_5S@}e#Sj>EIA_^*=j)&l>(l;W*UTPd}& z68bdjO;XUL4H}_c#Dklmem_<<pX@`{lQf#e9AIA!0J#1Dz!m51;0~!0249Z84*IB{ zU`o#NypYOpb%Tow3`bt2#IbUi5i0R7?s3fU0`$$l&J}e1q(Mfn=dnkY!|;75{B`lU z0c3JawC00x^4FA%<VTF5pL!k%hUcT1ov-SM0fr@=4Co&lbK1X?L|`>b61Xy}6#2A} zR!9mwrH1W(H=g-Aph<BU1xx^>mvb!q`FXcy@~9rBrTC*ccpylA`U4<N9=*-|b>KC7 z)R4eBGke{btlo*&NyJ8(lyOT!vD|9T^DAbq{Ev&u^@}lXsw@yfh0ez{A8(U;nXqOR z|KS2|71!dURJ}`H@4GEq#SZ)!q~1R}_sWIv0>bmye_xZ7ZI_{mjfq^34UJ>{neyWQ z`r}SR=jqVZ@Qsg+I+BTmRvzdObvnRG{8&D9lk1+0imjn&I!8bCl7j$U>}_k*<63Qs z>I!Yz#L^sjSGu%ITC=_b_00MAKTXAJG51ZVwWGP?5s+Jx|MHd?i;abp&3YHE{bc=f z^b@!!i~wvaHU@fKv;!mM`nyGHawY(FYk%}MJ=ki4wIr$dBkdQeC%`Sa|0c)h>lbSp zBMGva7xwnp{kEJ~dQXYwiejuEmBPuLNG2XC-i!Sp|KkbgDdn8*XBVQ6gB^yT(bw$k za#^EY0ct8SG*`Idh}%3}ar4RDcjgUUU~#b~RW@O^TAoLdXOH0i5Q&G3!Dsxz^k>rq z-So4sOTHnrQvj$;4zPeg(nVa6LKC`wVhB3_(WLLZ8`o2M!XRcX_~QHD^Vfqf^wbmm z>%@Wk@Mi830vQjxk=oPqHxF5s;ac~mt~nxsep}GZ0!shQ>$c$UpcVSjrRz&kd6R=$ zF!nZayn5_k6LDe|VECOi;}#$+r3&!rpz+&mPT=_ODY%eSrrYW-VbaSS70n*)WGB&f zt$msh;fCArH=WT$o-nNm&`j{Y2P75?N#{nDU!YFq&%vZDH1pTH(JvUTqrVh?2i`df ztbM?3<%iYEtgZBJArxO@Q&&EPP+h^bKi^KD|Gc3(oARW-@Tbo!0mmOGz#48_^8fxL z5bpuRBfbjEQf*ct$7iXZx+vDUrB+jW$tWv=nxu1>$|#C~vCe-A_#(izWUuY@Hh)jD z>plHM;cfAQ?}SCD^O~^5?6JrnR-r~cYH~4ips%fOS63D}I2lE#vL=U<=%9qzMk`=P zw*L9q|0un!4f>J|HgAt-&0@SA!T-6th{8k7T>5k@<m`K>Z5K;mgl{Kcw>&=lKo)+5 z@N8fwo6W<RP9z}SPLDpH1!h2(qZXlD4%GjIzl_DM2pB7tU;s3NVsn)5r+!Re&t&{r zl%Y1Q2@p%_PyU@T_m9cG$r|E&DRhnkj^ei2w>bj&clcxXi*clkYVOSGPIm#IpzB}5 z;(c$>Vn=bafcQ;Ov}2&>A_A0<|KxVUXk@8|T#7RE@Am_lDH<E$41e2^G(fdv&)Z3X zi8)*-l^#E^9@>=1j!m)~e{NfI!3{jr-UI`D4Y6VB4pcT`r*WXGX;rhwB6O!s3n)k$ z*mV1?aXWerv1a&VuUjwJz_w=7*!&WGB4mhH<<8K&GW}u+KyWW?4cw&x`4z1%GBRx4 zcSCgaqBN{ZrS8U6#&XBk@XtgZ69Vq<10K*pO_FiG0S!U0y6S`%6+|rx%p*47Cz`MA ze2cuY*oq)eibge+_>uw#O+2?$wV$PZxeIva+bSp%SJ^z?vbD`gfjkoUXv{-cIjUKY zP=AZVN{*C-t4`2pVZPkw+?jX&`WE;!#%*$iI3mh;RNB%LNN=IJm30Bfe{t)#7yb_c zDB!a#`TA?uhecftY0EB*90ns)Bx0w<rmab+T_8;Ybp7#`!xD73`dgrNvp4%k+9W1g zO;uGDi^bjn(zFPQY3auPVJTewecb>?)xJ;f2qNq>^yQu&^h5`~RiNR2BJL6+#@9+G zX1@23818oV3<V&bUf>fReo4DcP_O+d^j@MoWBX5FaXfVCuujPilyE`g-zTg7`RnS? z7AoW+<YuD;YD1J64MGpi_jV_<Zb5*JC}3zKR${&S$3sO>4eMx4cIcOjpuDFIK8=l; zZB&BUzRJq!4b$V}KvDqcDjaZ9%1*1RtFvbdF%fh4#2;#1Hnln5b@cZ$F*CmsSY2ww zepiGsy9Yzjn*1XZ6oW6m5l3?Z3)Q|7Fyi992-~y9loUPby;_YXzL5K@Z~Fi#GzGgs z?x59E3844T5Y+!7?>(cM+`8{kJRaax6po@G(o_VbE7GeCP!JGAx<sXS2qi!uQBhD4 z5FsE^q99Fr4LvF%9Yjj#p@)(}O$dST-#Neg-ZB2;e!2I{9e0e&Cx_$-d7kXO)?9PV zH8-BJq#YV)aBF9wK$frTKqsv(tY_C-Gnj!}_v9Ud{#?5bGfCaq!8+{h43v`Q{^sP& z)C~AGwZG=rhoCNCsAP#)L1E3>?cX!mke;2#V7j<M>{6Le7hlf-ZDw`m@o_!zq)fYQ z`Gvw%U9Gj>SfgV?IxCh2n2FQSXtl6DwCTrnQhH=`+)okR(2c>=vZ&)9RXr!ZI&U_R zYm2u^<Vhr&PAFCPpc=+t;9hGskGHh6g#KM;1|1+ylF8)Pc{KX&?g)ILRPZX6;g2IX zLHiBjGuAdYy@A=qri-!9S?wZUiikMh7icMADS2U^KNFtSXdJ8*r4VuE(y2G`Oxh5z z#GsKiKcuFj0+!#J<nb=-?8gsXfpZI~g{aMVJ%Mhta@~<S8*7XB=g^@b4k~4zA}O*# zrAi0<xaz--@7bmQo^gaCGu1+EeXN|a*MhxmX~ceKVvkrxTT=9F-zH)b2#AF?-H5N> zzP(GQtHd3n{Hbf%DbHT8=oO=k!lwzLG2EiBQ4Om%%qu3$HWEF?bGMuB=k4sA@-$>E zHBHs0s)UDr2-TuHjGsR@sbm9@MnWVEQVGM5MrhNYv#I0;N-b$sVG2_3m1Cc^7rT7d z{ziy(q=R;Sd%{Arb(l_Ynu;%s`C(qj?Zk05Wl&eJd6WbYVj^Irg42+&bdgETu9W=r zt*y6GFzri~u}!LSr1x(xaUmbpQ2Sz0UQ*hGJoft(c(<F<7~GWbv+Lv*HRDPl;y};E z-OQL7mE+_}Xk6P!Q`BDB4r}`+X5hH6iWBd`htU!>B}_y1s|nK06=7o!6CL)kG^eD~ z?3$Yd2tqC)luvfAr(fHD!Z>b=LASy;ty2wpj~M>K*4Dm272@_Ol>(6*TcM2Dl+JPU zoDGl6X}|fU^>GT~c8QaC8#?%4?#>;l-kbB5og=b5A4r-{=1Vy$o9Px3j|nH{OOHTP zdOu$6^HR~|=!E#qyZB60kr<-^8Hd*z=`j#GrNJOx7?v5iSAMfOd9UV2PY>y}0oOpo z+cr~e#+$u+>1`9jm8Q*zz0jEk(Qt{jJ>1+t*MywnX3W=0+v0eg^*u>+U*NHXRVhdV zjhLc)BQz}RKS1ah1LiNU^$g8Mx%4(gM#U$7zaydQU+wfY@C(FM(GRh?Z)%mk=QT?Q zxA8l{NLb$CZp3b#m7X#ko_v8ft|&w^58^73=)MRGkeFP*)_OJJ0lvWpy|lEywdtFV zH@<z_9`m>|o7S6k1)H%wa5G-#eimZMYv|*4ivd1t**q+jB8{@*T_MBQAmV>Y--gb5 zba=_eixX~|k;|{lQIgUY(-B>j;jJI-@+Pb<>?hwrToY@%E1!VK$oXQQq!%I(*%fY8 z-272=mmp_NOh*Vmp%P(L??JH71LK_6dQK13>*1%gh0c2rO0+k`6YaoC`;~AaA`F%y zUK=Mo_MoD>I(SloaBhY`U=Z)e*Ld}N;h8rp&Mr?3*K1L0LvSQa+!>vv8|E7fQ3PTo zFnwRnramcjF>2Ac6+*R2hR&5;@3EA9H-`^6SkV9B+r)g*#_dg-v{fzM(?rhvp&)if zuRNFS*4ZzOUYOEB%x;Z?P5I-c6Ju@2Q#E|QhX^NU%66*mGaH0rF<@`kdeR)|uSej7 zUI_6_@q;eGFzV6;O(!YFYb16_E4odj8DmNj;lF<$ODDG#j|Y!(c6zKt#_Md(6)D_O zP6)LVkrXpHmW=({zJGs_a;|p_6MI&v%$E^dyo%#0;TQU1S=gIl`kr^{;5XJe$2QK7 zN>r~ab%<CNVC*O~?bc|i2`741k1;59O*aD@o<7xpeibu#;=ELi6_;!b7!&L5dgFt4 z`BUuZ>o#>!PVONYJ>-egoC;f{ABe<g&n%(P{aXwQcY$c5H-ikDIvDi#479l`qEU+s zs^)%oAlJT)T<PG{4D!Ol&?GE3JOOeliM)VRu9zc;Hg~EnVyQ@qCUY~%2&R;u{882W z0vnq$4=V=9r&>4fY-RLgCdlcBoL(AB9HqYx`f<>>r4KSn<K|*lBqz0LcEaUgCnkWh zvamUhiFq&F63tDVkP2H{^UpfzWDBQ|ekc~b)yLee?OGr1xbON^ob3RW^Ww!|V(H1A zIuce)jfXy4NXdXNl)Mw@p3+|Vbzb#W5bR>10+!i4%O<-y|LSpMvK+*jl0hd48L$Ow zoinIkjr+0s&b22qEj4xF43lhEs>N)NpprW<ailTN4}01_rZj0OkOSKH)k8UBO^}DY zZVC#053mOpbe9a{uwLu&;!#TO6Stdv>fC=aL{I<P5Z2`4Ep2^5UqeFFe<cOsdR7sY zq7S*_iZhi7{5IXNW7wb{A8hd9D~)D-x|`NvVNt}(XO6{*@_#8S;}FEjS3}_8bAER3 zvHN6Bl5dBoadX=C0E5zK`6T0kG^$oQp!p!Ydub+Xy2jhx-94^25i8bSkC{lFYzTVa zgpTkJ-3||LG=el9XUlhIX-|}G2P6{Uuo4uW*buyYZ}C5zJi@{%(Alh3EX3cE!6OtL zNTZAosH2Z9;R@dmRZ5Dck(bD0btBTPt?1K3^R@~L(=*v|<m-ob0lz+Y>4MkZh*{i} zm_a<G+?W!{GeKHn&a}~~UnC=U<1(&LpH-x*E!31#$&Tfc!eU*rl3ico98^^?402sd zXfJd*haVPnXa{eqWkKJPIZEzV_GQkF`(UvF@#hm%12>9zKSN6;A#qzWt(j^MM*R1_ z1UPO)i_|vU+C9xq|2^fuWnJX>Y40^*9|MtT(nBC>(3hRAPlXap8$2sEGcZojWZhu3 zuy_&8^M+;xB`%O^ul|Wl1j&$yxysJ&2cjmco>tE2){EB(>{|@#nl&ZT_Yz?eaiU`P z{a^cwL~MlGyexbqp`YEa&XyvdRpUKiX#cvjG(KSdlE3OFEYyDkUw6s|araK$(vtUR zmd)!EwiCZ6`sD&1E^`{A&UI0G(oWaFP<89k&kY&zx(b1B{WgtTgjENi@{3*1zs{BX zcqwy#?<-}5S5i;8+)7sFj?0cngl}U|XGF!(CQTQJz5R_;ext4#>eH~D+eMo}?nco6 zasg{5!sAw#0|*JugU613wVV3T%P<_#NqNFq{I;jqZfaTyT1_fk>^ZARbI6_(Vq1#` z@a$WVm{JuJr1@?-b2FQ>Mrf>|95sMB013Wg-uio$qKGE{fT8N~2U4XI)=FhNatX>G zJ)y!q)eeTQjdG13*B{Qx-?wVCNlI^-i+4aw{{{(F@_?!3o8Z{~&;%0|ToRmI5}v$c z-)SHC`c0sr;n(~Z_ww+kN|rviW%^hB<YV`a_e~4g>{8GYhaBGBKAAII_S3^^Vr_&z z`9`KnoNC(I&?VhDVkMDS_j61_5^~DQysfXV4o4So1T(WNb2^Wbq9WoS)tfbm;uCEm zb-2*mKSl!<`Z|V>Vr&L}nTSaypZR=N@G{Qt&>`l`ObS{uJ%oJY^@)=w<taio`<_Aq z=S!Ua{7fSi2kU*WOwwU&*tN;UIj3Tf_{_K~cGZpc4soWpWl#7Ug&!@pt0{x2>8_}# zVn#@vPvTShw=hb@!^Re46$>f82|<LU*Lm?IVE5JM5JEmef3H0LXD-D}7vg_K<?913 z16o3M+i{MYp^h~^L6HHJ(Vy=PYB$Ny?%}#eo|mSVv=Orwp$FFwvDyFq?`=)OsmdNx z>)RL|s85Bbq)a`sWPD!hO9u-2G2mU|5rah~14?es_<=q4&LAHGq0`&MaTD?SM7rHs zg@-kNLTQT&3vxR_so7Tg4W0G@Kv8gIzgbhb=>V~K+JKgUF%6Vg)Kt|;=|UO7GW$#e z_%&$y)$(_r#$Na#bQtG5v9hGY%YV@IJSWRGLlgEL!6V(!o1p+Xl!iuQpA4aMBohh= z?FWB2MUnn%zxz+lwL=1k!o}%Nir;3(>zL`UMljkFVt-_$y+W$4x)S_@NmKBh_A3UZ z*-~)pmV1Q$bQlgKUiq)6(!K?;;;qLVZ28YvwMKS^E_&-nm+uQz(&d#!sXitFTiV2A z#*{XOcD-o0abZji;(YduwW@5<_14Odipv`jna*oVnr_{2QA9Jq6RJ~8x1A+BJ-L%- z&GG612<8F?P8IPhsW!nLY<TB*n|^Ex?W77{si>&ebU{z_^@i>+;p9`O0DP6EkQ`Ll zs=M=i#q$-dv83@|>aMK}(Oukoyrq+iONLg^Vn?1s;DoVlJ%QYzXf@s-mTow_fqNs# z-2X8al%}(@)8(*l@ZjF{CXMYCc6EL;XNkV{_EYRUob`??E#+-pC1f4yT11>rDq53v zW}`F@GFI<xWjmGwNs&!S8!lixVl-fkYi;CzT-m_kyUBk7mWZN+yxiQ5yE3XC?XT9M zpDvBSdDJH#-9@~DO>VxDEp~M!v<vC@Z!GZr1AR$pt}M%_+iZR>61rE%9)qykS*>8< zJDqWl!LrA%vNc`$m=xE(YKjcowb)k2cfjD_+|9VR`n}8fFL>|~q&HVnFOwx~Zras_ zWi-)dTL<$po9y$Ga#Y${OiE#AV_nw1HOJZJOgOb!V%H{k6|ifAYo8D2O{ueBBJnVc zJT}nOxAtdK;ELDp`K$(2h*>1ZdEPd>62R@fhYjM+*kci8o9}-A-W6@!{CEsXo%-@6 z>B=1WlC~;6;Mcfs<NQ*F8lrnP2dNgOyF2emuYh$Ctx{dptTKy2;+sO}X}aCjGytG` zuu}`qLqpp-Hv6<{!dpG)n$Mf8M9LLAJ5wD0Hjp7tPk*Dh?D;Om@(THOEQPcUMyC^0 zocxgRwn7DMxtRGl>U^b3Ut;MWBP40TRTviKiI+EQcCxLZiRk)!?0JK_Zo~CNwe7>$ z;!g}3X}c4NuZE@dEH+R=w|v&~n{e-fEai>Z*gVd#TE_wO5V$5}&qg9OshK$&A@J^& z_}*4aiXsCgBJVhLD({Qcta?38j9|cvTHc2_6z?3uZqT0lVwtxsTDKU6@>_AU3y_9! z$#~@IHowut!Kh7IeliyUvAWkEFRIBZSPbLU-L9dn9KvlaFf=@(`p%qwPIOl!p*)%* z7jRM_jc~BlQ){RH+VEZBeh*HRonyFZ_Jfa)PYgd8&o8tK6>EP-N4tt5=fAthov-lO zqaREq>nbIkxldlJ3urEx&K<D&>>4kB8ozOt?cM;OjXx~{EVwvZmyIm3zlfcsktSRE z{gf_#nR<$7B4Y`0>Ug9G+;4T#L{*^V0~%fWmY34J{pPMBHL6xs$#-{%K#9=Z<Rzrt zw%SPjrkpA}#iXvLe~x5y{rF^AD5w2AC56C`Zf-zq&z_Imtp?$60;6cyaU1mZ1L}Mi zs&spIFP!@ZvCzZG3#ZS$ODDv(aH7-~u_D0NjuJI4F(vDXstA<D{l?)8N<1(n!p<`= z-0*`ObT(tbl_F=im%W$MB0{?Gqh$)t7{aIQS<If-9=ahS2o)AV&g;~<60bM;JH4<^ z*9z&JNDba7+Y6;yK~eMEfkbmNzcad85o0(smZG&GoG2+YidpWDCNED&+4fFkQup^) z+dt9|nsz6Qv!eirGYcoZdAVUFA`qFT>Ulx;l)7DAQH{54tblenrMG%R^65#;Ml$-x zv&mIwGh36aez*As%-cG%IH`j-D>Z>LJf>P<$mpdSRL3T8R7k&|iuqV7{1^DwkaV}@ zW2G~D`%Uc9GuFh{D#E)T?@GW+@19bGa#>7*f@>3$s-q30o?>>traMMmf8462UtZdN zdzr143}mis{ECb=I?Wilk-}~?JIgC{Sk-GngIVJqw`GU%`SuWFdMdwO8R?U*oVRxL zfsIrXY=s0m0D~7NGC>Bq-FyZ43llJ3_}sraZQXx)RnqJk2q=t=Rf=im8%<t*%9)V1 z`StVXw^`KX9T2yl-e7(4dspLm1QoA+l0Mm&kx~sIoNs~$!8Z>a{sA0j6zF+8<r2{P zL;;Ak#`oZR6N8(q>t}sx0zl*of-k$eRj7*zhT<u|?6rM;OD*Nn@S%)i*rZN^!8GV_ zANB{6HP!#9e*f!f;V+;U7<{s)1AH(3ze5N9x9`j1Il+(pb8|qc{t#jIwP{jCP{RJN z@6h%blf-l_|KoeBLw%|gtxFy;r7jBliv+x0ENxwt3;6*HuDSPuB~ZYdn_ta@Z?2v^ zOi2xNBm}EkTpo&s^zj^9OAFk*HnVXyIywYi&t(yfq}FPjMlxd$dgx0-(OMHh)br=R zADVIy0dnCB;4|eP{}s11a9OFnfBX~T)3pVy;6CeTwuVgDdGP4KOy<+EYq|Jj;})Z1 z_Ki<ApJnGcNrm*}YO9Ba%BAT~sJJ{u$K!Ci@|vgM`f+7Gs}6J;N|JJ{zzRnB%!@4^ z3)2zr>AH!_iHJ#yqMA#e-k5}=!zjc3`!KTRS@Z|3Pp(@Z9ybxMVr*Ln4rn<-ieG#s zf%#KMfkN{pi7S3mgzs!pLh9yRX`!QOrm~%E&jrDWumMn=>Oj*|rWhu@;UNd|)H^!p zYU|b9I{56`Q9h@XkeOKpY_Vpd-}fiwH9jI&=*icq(s)huf$RXnq=fmce?VtSq-8f~ z`RpWg`K#Rc6YjJgzB(VMB~+>DJaV~H&cNg+<hBXdu{GggyvoOjUY-NOoRlM{)xFE5 zk8JC<e(ydDi(ozz%bO>!Of<KS2r}}MYPw>~%?Dh2AfeLa^mq*6)uD%7X>}7TkM}i1 zbwpA^m|0jfCOtIXq<CTNMSzs2YMYSu33MuI;M~Qf;j~=?rF>WPJWf}Iqzhe{co_r5 z*LaWad~W#F{!Ei5vLDcuS^IN-53uKO9Sv{dXg~*~mrk0)i6pNk2Cn}>PJNUjgcEi? zL+9DCzVBqH6i$gwa_l&*bgEX9{-nNWg1S6B+}vP@6oR%Q?5o1?Gl?FkuMrjlikO`4 zxv!0MO~4k(;3#ADjC<ytIlJGPP#QPcQ8VA<R|dm*x&FnbR3>>WxcFnc(&}QalrJXl zVdwh=2%Vg*fVgm=Z=f78P3;#&R+h!Zr|gA0nu<4W(kBea&7n++pt*dm>ain2x?j<< z%&sd0S$jEM**3~KPnFj8(}=lIxD!Tegt-&INPZY?uHekUK3wO24QFy%r3i$ZaMlX# z>CH5CKY3!el+i|56ll(92>)Jj{JyU+qR(3l3f)Lg`zoRlyx0(mY=3(GFzT2Q7ZQIN z)?u4!OQOizR4>$AIlN%!QaO6X_2rGNm6O$oaUBD;sWNY5%e!-_2xk=pr7Ki%NGtRp zB-IzY^Z4~$*#p{^5^CkBYUZVY>*o5Fz2oTpiBfpO>+D?_ROji{;6_GkBfm&m*1eGn zyEPxgpzk|Wd6L{AK69CoZB1H}z5-L4i`R&KF}NBJJ~M@OypxKj(n^EGvEn&G)4^In zUChDOZ};&kFInl&v+fw8h8n^NyPmz9sTDw8RmXNH$p_>fGoc{2V;W2t8;P*98?n%k z%=6erdRK(nLh^UHk5OGV9j4#=a7HBonGT{;=R!gbN}#;XACQ!o&{FLn%-;G9eI0>7 z=cVIZ$CID!wXdtGM{7ke-=dQzb%NV(!ju!R?Cl}4c^<>HpZnc3gpm#+Y>KzLP_MOv zhMRAVK0c_s^~)vok8>DtNZo@l<ygsIVqW+lqP|9M-@iTS67oVq7fLgL`uqWu+uKe* zdF0)nwg;`7uy@bGH{&7|=MAb~X`$1DYtQNB<+wI8=o7)kX<8u!W5RoLU3zfBL={|O z()zm(k~(uo{NuHhGN18g&0pNf=sB=!4?L*8zB(}9?<XuuUUsRrqY}()$SsbNbI9GG zSo;HNaT@4k2F0X3I&{r@?Yk9rVv_<BRuB*&wm1`0)#|a`7Zr`Si9+H=9?mB6M!Ea& zTU{p_o3!R(`*u;Z(Xt5a+$34pHppWfB<SRTfXuJHm|*^aKc=l{es(=Ao9~rY3Oucu z$wQA)tDI#MVlp-SYh^!?bpC<27GLtcUU5m%C|LD4Nov6A-a~8$=9msT$pcbyQH(ap z8{P3|;vsKT*OJV@nDAUwpx1c#n78Uml@HaJy5X0S*cC3XSr79P&t~Kr;@^Lrb+=RX z9BeQWq$eMHqX>En-dw4i@tO4>lF#D|eVQkZfU&KVm!nWjz2hpk_s7s$PfqFz@Q&Us zX3T$cNjSbhMzIs>na!%)L<ysU2C8ski-NOLXxP%nTT1V+vh96=ZrEPvqhOPgw-eB1 z#T`zy57PhT0y@Myr9FiqzI7yM-v}?1*zq@g^ZH88+pTV5c=qz$A9OzBmU%y$6|Yg* z@Y`q;=nb1p(mz99+fkD_y4>`ek;i7TIbgn1wVz8dc|{`x!C2I2(CI$B$#D6?wYq5M z^HvCQ!sG1)YUhlnDkWiis^J$8s_lzC8HD;^qV~ts47=|>*sOA~FoUS*_Kd5INPas0 zw!URByO^M{MBNi!10J`53+4z7MH&@Eoyp{;zu5e=S-tY=>#seZ3ac{LX`8{vw$`ah zcM7^Y63yj1K3#Gw+q{-q?PT#zIQGX%iaN^P&?4rSAcm6i*}3mKFGpm#Yu+VwdrJvt z#n*1uBTiOPs;RT@&HwyFC1K8~g9XOxR0b3I5WfDt>4>LWQ$6*3#Yf3G$ZfMy^MZG2 zAW%C}P^(h+_v^U$o&P$;UT$My%RVsdKh@$OqWHLz+eR;CGES!4xvu*83Z}cfcap)4 zP7<#0Y4qA2Zi+UMcz8E)#*+KB?1;LzopQoZSIX4B%1V^}cI08eI(;_tsh$`=*k6iX z8a9snYqiRE$U?%^>|NFZLQws-kopPT{kVPuQ;Bvh;@m=k#U(+57ktE)trLe8RQr<I zN3^5ky8dczl3&?T1E|1@GIh^-BM4WgEcIdUjdUh9W6#f-!)NSL)mD`vZERf2h-9mr z%x)ceihfFH+?h<3*`RHf!`ja%9JS?Z&#J$KTv&M1v`O#lJxtQ%1J+_dT6fL+-lFoi zAIV2zl-H|lyHXv_OT1l?lzHdeEamxh;jYAPpf_VX9q-fR{w+6ybId?_$b)O{&HnDp zpF*>&?*-ap5B;x0SAvVZj2{EqzQ97mJNlmbcBd4HYou{=dKQ&NNz<w*!9s)~rL8h5 zZ9-b|(s{G)M&y}_;jde6r_1^tgIJ@OY24%;GO5vAG0&j7>ZaYTahXx9YuYGn2qtNM z47{=rvZtI)9i48F*Z%AP@n|+q+u)>v{Y1s)^bfqMmc}-p4S&<;J0oK~SHL%>CO1&j zov6sug_gU}vZ_gaze#>CHL0GFyrY(mxt&ww#Z_^1k#hI5tYiMBnZ-I&mQpKUmvpkL zW{i_h&;=;n4CHqhe|TYjM(S+&#w@n9<Tj9EW&H8G?f30_cAnl)$Jb9q*Gk6gY194p zn=}kYIfuD6`nr2j>`riB%V3|eobKssNV9uN!HC}_$GlKBhPVADdGmN>-1Nfr6nB*4 zy+*3c4yQhH_6>jXM>#{K)+gg_k(UAdp^ZjO8&0t`!x&2DKDw4DEZKXpbHyjbt?VvV zKXEzS{Hvpo&9KNs-awKa$3Fh*z@MuD^B=O#6;VM{M7D;|J2VBO{w7JiS^`+A-IPwN z9{>0vy8WE=rXR83#Yp0j3PmsU0}$QR@_N)yBOOXYmzAqhwENPV9#K%bc0W;-yoN$n zs8p<$?R>p@iD_`Aip~}KMwNdJABCEouXVOsuH3E&^|s}umz)^1OHCqj;Y~pjBR(C- zM{QpK0=4@3jVBv3bN+EwD>>b7+ES#95dAPGRR7H|p*PxBk)yjwgMsR6zss-2B>SF1 zohx)zgmt`kye5q2(-U8^?785Sl}R6<DDHF!AT(ih=u)e|@5Pa6w_cFPV6u`{3P+_2 z<2+F^E(*p-70>eSXy`V852+3FeG^)d`Bi1VKMfoqIQL#jlQXjNF2@(`j2VxEjR=kI z-g8e)H)Q^E+4FnnQNvt2nCMuOuBA`!N^HYIHzUYbfwS`;_(hdoukv;@PxHrpyUfpi zjxeHjJz`ZS;=5cmcUxux{HDHP-Oc2pi6;lY7r0YOKRr(`7^v@{6GsB7LV3qeW1$4d z-L53YZ^WUpOC#muj&bcg7gSBLa*JieGb0UeE!m(t53}t$Z{Co<gm>HZ?_bf17}do7 z^nZ{_@6s0IWY&i#7Cq`3?R>X4zETXWo#09NQ#sEmNBg!u@OopKI6>w>9~E7(!{M!Z z<(OU-`A6vkQ4^9>qfXx59HM66pYkUF_9p0xdW|$g5OEtce)^qae2pm(Q@APtf5EY9 zzYY_ouC#=yizr*<>P(K2CSY4rc~iXX@r4QE-`gm{n7{=-o2~~^54QhWRVta#%Ve+g zka2uWiz^nodFp7ZBkgVCmF0{+!k7YO54X=dD2)m?N|wK`;H<^?g;O-yq6kdp>gAAI zWD7s_X)A^@D}oLfE}y$^gFqPGq+J?f>h5|tOdr68rj03Dg=W^{rt=Y+6z#y-F=Ls^ zfjQdSG-YI~5hYxP>pe)rq2<$xV~VjtgApBH<sf@yxAaFf+jzW3yQ!K#zNB&X+SKmS zRb9_)#5OflUE#Bb%O0&*Dn)vwH~FE6!}adJVu_&Wd1UDE>$77XDaEe~eX+jEcNA+d zGY~Z|DKjv6oc)ls<!iB))i&{q>N*o9B%gQBf){o(D*20!9$ORCY=kY_DUFZf2$Kqn zUx%dW1=XHIU%LBQ`y>6JrzH$tyXq8mut-?oE6jm?S349G&eV>WooMw$hwjUSvQ>Fb zTA0X7&zA%U4(82qLJlsx5sADg{W)|lYJ>BW0@7L<8JD05$z3<r<Q~DFRj!T3q_m;a zXnR#|vGEuKVO>h|<k%75WqLOu&F&@S_o3<fbE~E<g_qABV=|}y!dZh24a0t*Ee6mu z@P1sVi<PrCVN(_zoQj8#@l{o_v6hAAuM8pe+`#!6FjY?rUCXVVXG)A?ZUaFpTRxpt zw-;9VtWKd34+nOm+NChZ_(18ZEW%Qg-(W46LVaMWUw!8EBUVZK|Cf`1z^(q@a4Gt4 zo&SG16$uDLpCWswA~+wAw?SVWH7N?9ZJSRdpi0<E{<Y~G)u!p5cLW;so499L8X=*f z+)Fs4?0(>?$!71!dhCYFBcCVjO0m;R+goe@41ETKpA7Oq5Bz?5j0KZWv#WEH7#L4m z;r%u$b;g#$fe$~J`z<KBgY#4gA1u~{k?KOO1nst%zPxp7dr9-jT&_2@pIby@cZs(T z<AZN7@f*0_@{2qQ|E4ohcSMQx$$&tgZSMgkVl5P~Zq3ZSh{TS-)g(sl<UvSN*!Kg* z^zm5z0GxKbwJe{y39uI%uPH__f~d_INN$lq-}YEudHdU})w3hm4`gK0&OK;B=YrvW zIAu@@jobk9?bMDrG!xfSksVYhXj?;~uSL?dc7JPEs9tD%Oc1$wlDALQ>&#fC0YAMW z{s72Z?CVzgASv?LgG^1<qhrdmYQ?0JI5u1<KXEC!4mWjT^ipG>FSJun;AucW0DgbE zGN;Q@L@nf}n^NaYoc<u|)4*ADra2MEmO@XqeHbGD;L@p$Qp21;a<y&o28H(kvA*|m zUH}sQ&+z3c6JhLQG08;&OTi1IPqiFp*qGdDHN;x?tujFBb!>TFKEo}f0Xo-U4m^wX zJ-kmyt@degF(@@GSQ)DHT#)p|)eNCmRgCMr?Zd);iJ5AlGc$o85WJoRvvP=IO|Wn8 z?1Y+hbf!|?$5|mZ0G~^Go#)^-@Yy%mq*ohe+P}IDh&VQ+!oQZi*GNyQk5)c7bDRT- z^aX;mH~-z%&lzXHAxSJX9{Bm+#iHOp{<rn}Y0SFV@(|mQ@%PDmwof0y1DTc{oh#Xd zu+;_=uZ++Y*gkz_wO*WuFGy6jzVlW5C|l9uRAc+<I~bYGx$)VuBgH)1(qnS4;-=Xx zpg?9xMITJRYNqSocZur<`BEWkqbuJ8A^yNHT!8)FzARs1#}me9xxPo8y;nO2;^Ont z()5jwaXkIXdIruN6P{A<GSbj#zSLoNB@b2Lto~uozsI-$dcTm^q@SoR*EK!kva#WJ zpYPn^!`H;6z%<B7;xf)jG0$bGhi`_GGjJ+Gfc+^pQH$Pw|DD!}j)4R%sgC=+nDfAn z0{n?@sL_GaemBD|r4YBFOpREu9h3fjl`MPLeO`exK+Xy7!0Hip3FoDf=SqOD44y(9 z8vJJ?@Zwd!u$t?cpNOu~;90X2N+sVi5tYzQE}!p)yX^NOYYQHDprmE;E+6LlA3N52 z2|wUE1i|C_Typ3`gV#0&zo?Fy+&}f~(DWPDBRv79t6uG!HwM?<@8Gpap1O4cyeCV@ zxF8)iU%F=^HK>H(xOd-uP`Xm@OsvEkaLpC45iNc`DW4~#4NCVWvw0}R8oIY*Cb_|> zPMvL58f=$RnvySI&Auib1qVBP&@xVywPf`n{=M#2^8IeU>+EN)9nZ6rUg&tk@;kdL z$HumBDg3XpA!?5~I4DH|nU^TZ8UgtC68mR+iUnVeC_VjXoSA#OnaxjcP>Jo67ghRb z^(i*o2@`wpGS+M1AG#MwSEA$7Uzs-gU6GN*TyOdo=baW5<5T>#0C!n;f0uvGfJ*si z=q^})_Hfo_6+cG*W!h}_#o`hNb>$!1(vk)fxFUJ4xup%~I}XEtF$>y{0Alf{o-7X$ zx%HOksMxc^f>*0jZkUC%LRS}0Y*dHhzMEnXwhK6YK*a+Dw`}|@0i*{N(4{M!IOQ`N zJZ+g~(x>~)dh~gg3#4n~x5nh)+#=d2Kl#y?hq&gTfrQBV@#wBQH=ZAcZXSz{ZwrOj z@eoJL?~e;ItuYaVW=EGw=WBgSIuFOkToLk8LF=8y>#y(d#Z>DhmaU61I&3;-u5doN zS^>?S)X3V}-C7#an6dBd+}51;XrD^AZ%S|J=UuN5lT}(;1Qt$Ff4evnn(3qQ563ZQ z)bpc-BmsDi6xfuA6&Nfx-rP4Z0Wg+$%RbBgNu~brUy~!a@vfV30khqs@mQM>%9jLy zTAPy+CM#@y0(_C@>a^b+Jl;_okns3CuK}%aZq*TAuhBeZqh9ClnyBP~npk7;uBiI* z;Lx<iZjb7c$wpT~bMs1d`+vCru@3%$RraCfk;N#ccGkk$+wOEawp258F@5xjyO4-t z@GViKE?Syo4Z_G#i$2|h=K#mINxx|I3Wnj{V82n1U941Bz|VdjvOs(#!Q+I~{d4W2 zEfr-{@<q1*N^PEUIKIN!iE$zl&rUwsn$A4`JqSirfB*jDRMwr^i1&_py&RmoG}3Ud zo6>%{kHIV;KbI97K$I<=6ylw%Pff{dcw0+}P{;HfPf)C;E;+?i7pjm~rpl{j>Aeqk z3EwjNq1=kZ{i=|jbiG2%FClQ|!FXsF=N9pwQ}+lMWxIv$g$gE(bR+df+p$&UC)ZR3 zl##ZL^*7U8fbZ(@RnFdr^)>F<_e^<Use);GDoJAP>wpg8@%%@&C17<icD7gCcccbu z$60qPAvsgZN>g0!X6`*3*Qsg+>dtZ{sBAoj8WWDSz`2D@>po1CJh;yo7S=)J-XG_K zy%j!btLp|3V^|YFb(iVGpqiN9QOve1bMIvYcYN&u1O?Xr5$1I^!$7GB9k_0u`08Ha zgT{mHM$idJ{RM})S9<r&0Q^T?nb2qM-s=#Sd1`we_h{qy*Flf!tf#fMp`moByy44x zeg|;2YHpTG_zEt|zxJ)Qg9uqssq<o}G&4P7rws8B@Irn#cRS_yGgr!1dz%b|6Wd>C z(HE~**lW3WKN9GW<=ZUI=4^kmOmd6{tW_RwZ<oJ{7oH#N%kG@-&w~Csya%d&<au>c z_b8X*p<ietkD;OjA=lB5#ry+`BK50_1-u4AnyHV_bwYG={oG5pr(YB9lh3GDnYHXf zr4;#8jvbjQr+o-FDsjb<`|&-DIR=FJE@<#|HpfUF>Q3{+E(xgJj~kJa@d8PHI?MKd z^x8b)1;&hq&ESD=&W^~~a!i8_$GvnyuEiBCPwGuKhQ8&qRq%7e_f}-dWJB>}5{9f} z|0EuTaqpdLueQZFQveOGS>-NIdweT044Mh8-K(3qVK@dV<1Y2VZCaX^OW833oClr1 z7Y723{BvN&bZ>tzyh&(sDpN$jW|T_ibv<e2GZIR*Hw^a4jCge(pC43-N?GNkk4Lw^ zHv&~@*!4qH2b*6-bE_y+hD-yCjCx0Jj}g^WFiK8XW#?F$m>^=pbo>{I?a#D-+<5MU z>5kTwwYMot3WsOhPrhsHvN@-KH23n3&>{(oaR%uI61ToP=26g!o!R*znPF0Mph92x z2<dFE;7M3F*Efb^vTv3IT=)Idgek$6ItuDtKN=Jm{gR^tBP8f!HI>`(iL4dZ{S8Ef ztlIgQWooz&rC3zxoRtK9Hs{BbA(6F_FR+r&zM+ba>{6mSY11agSMaLeUhH=!LPv7$ z<N??$m7}sR<T#}>N?xO~`5SIoelFu<mcFV#zmYlaBU`R!b0|5~JgVhKfsm2HF2rUB zg>qojDvmbOi4j?{6-#03=22<--dF5|1DHE$dL%p5I^;%v{h?+L)XKV*4yeB4?g$ln zLvmN3r8g{gE}FMR#T->FR(e_SRzJ0K_HYIa=^QKzI#4HAa2MOL{9;ptT+eOR;}m4U zQL_`!k@aSUlRZ677mfCny<)%E1X?n#C%ukGrQK562{3vqyV?1nK6q0^{UX!;&yNMc z=xx%4qep8hc5JiU%QMpF!NOabC}{<JBw??<)1lg=ue&}eVKm#()5dZrf9MF|zHD+~ z$6MFFJhl2cUT+$kjDKX|hDwi(-y>lG$N~?icogT*b{AlC3Oy(&y-A~Go9jWVEZh58 z@MPCij}-HN$Vl|;Yl8D+SU0HaTbQ9sY8tG2(isii?<@<I*MGFK$n9$~oK*53mpN)g zh2zhsKFw2^ea-ZtO<&|>{$p+RvDwpUl6bAlvV+5yY=6!Dn)zJ0SgICCyxZJ=@jkip zc!NoLxn3tA%$ASSRe4Bqb0zjeHk($RM@vdEDWFmWtLUZNPlvr_+>jOQetN(i{sU`_ zn5rpqZ6r0wjtVl>*Nrr4m+xwF(U|PfH<*Y9j9pFGIC;NqW2@$j0zoPo<95<PxBVcR zy|7m@$KkHg!8+~%h?(AYDl8=5$^!CCiQ_A{rtREAm5T?DRqU#LR{A61z4Eo!FdJIh zfu<9h2#_!#JvLue&pbc8AJM2qOkoCl4TQJUM0jF|%lM{!5N^~Y1g5Z_uTv*fL+2}$ z?f7LO=*$FRYg}Jf%R`QKN69%=FFdV%663~2hRG4ukfH}=Pxj+JM`+^V<FQ-_e3^e6 zGd$b>M?15^Q1)wyU7)AU--yN7()pt>z`}<d_@MAg0#U-Imqpaez-cC!y*?6XX;fIt zO={}De!e)&Gl59#?x>MisEK*8svC|wQ*9^?Zt%_h`NgwfoK?Oe(EY!2W+ll*H*ID4 zBEP@;=v=fKAgm=Nxo!T7Ustw3a2i)}=R?ufFb}s{N>V*#?TEm+u5g_-{NkK1pVF}- zX8~{l=0Oj;ySdhe-KuSK%;{Dlkx9wP;)JZ(E4SG^#8@!O=<mVx2#?+3ahi}$Ah8>K z%U3HZoNbirgDOGrV<8+?!J^WiKcuZ$oxmETcxn!ooFDtqmu1#PhT`6bO8<z#@E1U( zxS^yM<IGljfegq==Ss+R$%C(_emIr?hb{m6T^FP84;o%d%7Za!*3?JIwPeJCsu2q> z?q!&C;5683Pl9RLYjvmCEshqBJ~_=g)o38e+?wPn2QJ~%J^j};y`-A9*Z;`|qfbxI z0%$mBI4%V=FP&{RC)qlkOLJ=LI($)l*&VWMpY-2LUV1r{)t`Sb%L_jIFbmG<&13GM z3>R`MoE8MY`5w17{?2(|H{bCF3=y%$>#o%iPd}=*m0OiQT6o34l!N2moYQLTQKM!p z<2&|;kNwYU^yaPZ*!(^0rlb*j!?!)}ywcON;&`XtKGl^8uRN(S-NWBbgE^~D7ve6I zrF~IyAqZ@+hFdv8HQq~#$6kPBqAwl%b<KJ0%tf}^zs3{aSxD3O|9Dg{E-tz(*arFd z{7-3MQZwb&-iLb?4&&hoeLnjaz^%ss0=Iv*ubHz4Eg14}<^c95yIR@b^EZQWZiApQ zKq_Q+q@!G`K5W@m`@v}M@+9E9;-KzeeVXY`qSaqr+!-d4nj4#&n;R8Z<uqQJ=U7U4 zE98($NeSQC?oF}^B(EzrvNc$jn4u?@AGR~e8koNn_o3Th&X_e(Cf%C(jHJv*54vA$ zDHy9S@M1I8GTU74lyu<@38&_#f7S989WB(e?y-!ylWp#y<PjU4k~`hpH;6d&$&eNK zZQ^QvQ=<<9|1I0%(O!>gTUo7(N4`~?uN}7ke!nB*(i`CKyuey#M~Qf4=ZR|EGcIUp zENSeKO}5ky7u!&mh(C#RPsrHp6{rVJv)<7;?u_iwPQ~X-T+?F#KOeP0*seWb-K{i= zdDie4ho9U(utT5fS#LSECJqJ$KS_Wu87$`imxi-0ff`X|cab4#SG_9EHmCtgfCGww zF1XDhmgk#H1A!^&l$j)aHdf+mP|S%I4xR1!n<642e|@X2a-{T^6809CyoY3G%i+J? zcx;zi>7o$?W6<Ji4Z0+*?AX6?oHIUSeSKr8zhJwJ`SAZZRv<qK3>q|T?{bATJsw&K zD#dOm5JUb|yOflJ#+|PgJpP)^d->ez=JU`$J&}N>4yAO}k+MGyb8Uh1k~Uat*E~F; zkwGP&*NoyOgIs6dxWyyzRCj+i%^6!d)_hf>%;y^z(s2l9LOiNTjrD6dXGqV7YDZk= z+qd1^{oedN40)j7Wgd<`p!D$6_~20=LYl8fzVmI~%{R(hsz0w<WVh`J2%UM08RP8z z@CYi&R<Z$CG9EZEGCm;xvr~XPBCv^0nHo7=S?crjU@74yBx2be27{^hu9;!L)CBQQ z&f6iRkK0t$dM6$kdXTL!54|s+OC^t1TlM+;l^+`?6rTpz_b)Li7tRhg1pN3tT^}_6 ztqsDg0#!oZY_IdeJQF`Kz_-`@7~}+v;_=wS4Lu*R8v{7efPwshKb4^2Zyz@YX(J@h zG>un#Y%IgDE=~)L>sWOtH>fCu2OF{-rO<u0vInaTNtw>=eKbFuaf9~@(3|Y=$~+j~ zjWjULb9Dx}Oy{wMZfaj;z$StjRBT_^O|-Xpy^nRxb)QtiGRvhFr`NeU+<kmLMw1(* z#O1_558rXXYhjfzwWm?#FdYe-y4ZjYS*SF~n>smlu69d5qrq>zxDP=-J9%Noz9J>B z4K#rxz@EO@X2LmCJWv<G#W1Y#nQEUppJ!fR5q7f~41~V&1m9|Bl3~t}2TC|K)uvB- ze`MLQfk~SY6jbqs)2m_g-+*En$l8&eU)J;D$XfnRZB-4DGN+EJ@AXTfeMinY4UFaR z_X$VPdQv8XG{8)Od`7ma*O)Y3J4nMMcOkpY#J={OEL_!lYPb^4Pv2(%y~yF{*yBeE z_<<;|TS0dZL2Zl{i5M=IfP`xox(~E#kK?)z=xL&hdm9v22gC}5Cqu2V$u+)&f><kH z^T+@&k|pl2N>QOS`eD0cDdEh@^LRIKx?3w!`||Z%{I(qp2v-a_x{#B)>r1ag7!59! zdc(I@cfzpriKZ3?Y=0v<$F<ktos`9|F;~&dt!>Q%s0lW>&EzK@<a{9XnYwtvV)3#1 z&nGn@VM`*ZVKGAa=F*@{v*fnNIG1*R>(g>d>xhKKCJ>_U`xAg@LWwkd0@AcUk3`pN zev0#rk$5cGm!3$7-RkyA?10YR&AR0FE!|fs>LmvXV^$X$2FsupEMrWtj$1_bLV0H= zR^t#f02;#P<O$nKXWoj2?;9~Jjhz6+{q!hn>v?bs>u50Y=r=jG6DRw0w%z(f9)kG@ z&f`mBU0D47TBT6P73Cf`B`Rrhvab(gc54I(<=c0pqq~0i1~O_MKR&)PxGo6m$|p~1 zbUbSc3j`x}X+&$Re+)6XG)PdTYwEZlW_xAyL2&m@a7}jy`hY^J4>~;m?f|~6UYC@V z6hlP;o|km>0PzztTp8ZD@PYT&WFVOH?4aw?M-Btg{<){C_|~eoaOQr=el>_w!tnTZ zHH}ZMRVmR8vfnySfSy!&ygHCn0X(A_Owv{PWWZ=&d-shEFUWaX<)vDNp95dC>yXHQ zxd6DYsOrdgSGHYrG6+LH;}Ny9daziZ<<&VU%_yFw3v-3CYn@PX`}N7lmEDe`BssBN zAx(E9oZ=Bz!hC9v%c!t`B_#PH%1{qLRL}k!mz?I4=Qufb+?7^pEANy`^4h9!@#N2m zaWw*km2#}xIpn#*N7?$vsxmm|d3+jZZ6?j3?_||5UT3FD;c&t>Xw*D-ilsCq8UWD; ziZy=xFK_&S{Bj7@+yvJGvsKP*U|!Vea}a^tMCg+o#dr4fpgv<@r9P94xHFoIzuMbh zfpD(>BDw?P13ZwK(adTWQk0;NoAjK2UR+ISnc@Iq2U~hqVj$Oe+!^Cgni9H`Yu2LT zJ<8nRI!G2vR|~BNT7Rla-=DJY`wRFvNfS<vVU)1&7{<S7W}P?BX>_z-)9)IZg8U%! zbk!c{6M)1?_|&v3CAr^>qkrqI7rYeU@ur&+4^p8dP*=Vb7D!bO3U4GGv3bv%X=+j& z((jP@@?g?^&;yo{2EMip%7i#lL|X)TUcCC#=~tKSWcS0WbTj;BB1;K``31nJ1&x1r z22>6-qEPjt(J0eqo4}iiWd#`SlvOd!xQx5EO^jLMjOYgPNI@kCvC6Stk06*og5n3A z{Yvas59XJ*B`R$=+!<=u*^8TnxkYt1zmc+8G%9O$E+>%q+o$IZyWp_wojY1!1kKD! zf8d1bqzb*pKaFYs5uH)%GgJz#+It=q)0MKfg-<~bEeGm$({mm)OjXp&a5itu4(RqV zr7v9YYWY<>8ODR8wDc`}6PeWL+G+^EO@VZ=G*H;k{Y_BS7hmC>BC9m2a4gwE4>Bsw zQ2xao_sCu;x1#aNv$5;RPM~wB5#}5*(uXVjsWz2KhSoM|iYrfEnaSyvWwdfpXAnB< zywaTyEe-qcI%V|^5Ex-_pOG3!?r?)7Hv{h(-wfgp6&7TRS%$quY}UcaXhrG8YenH5 z&*prHo1lTOMQfkey2*Txs=0ctvYn$m@8o=;-hf}~vv{<j8e*4tJXX-sC3_-BgASp! zR^4s;PcMZ^IU#4%2@F>LHHtgMFfI!Vq!1|(Yxrn+fa#;jr*^+4E^##~W|Jq8zpqrh zZN4uN%fMQp$7}pU2___@UCkez@SWe!6trkR|L9es;k*41KG}qujg4B)zcZBKK9-g> zLX~iwNqL*8ymf7rReTBc@9{7G+1hZ0J{!xl1$?gYS(HOduJHvL1v~L2fYN;Uulyj; zyF4G@_@nOBXj1_Gip^61Sh9YqWsFUgxwjo^IuFU6)Ws01dAtV8n$Z|s{qw1$!MBGE zOoLWO41*aVp%Rcri^YmyvUUYwdi`xtI)$*`ucmd2!-XDzlI_-4D#{!3Q1QY0ijsb& z-b#&kUZ{Dze~vzY(l4%mm!6#TB+YgI5mHovNBws2xXd>?T|!+{;3?2jb#!*bxLIQr z1#|Ab0z#%zYs}3N`C}?xQ#M4bPYiI`u>*MNWIhkDA@JZGQe2;S8NuMG_8#z9|D!v# zKYf+4b~8F&p6=Z`Z`Y>cbtY{0ZsW14xwbJ|NhTvT#n~V)j^sQ%SrY-9YyS^pN4o*W z0wDk{Nn)>odB!R2288CSYic5iV$u}wKAcA9hamS77;$K|eRPTgHlXtsoIyC5JE=^c zX+x97^V$H$0YiNlFZxQ@_v?S<KLorN$VXj3Jl8RJR^;p+g(2D5ZSy1@w8tG9lhy~3 zSf$i%K)#Oi1z5|i<{7r~(qb8)=ewCwOe>wc8Z_&tUMCa^#8_96wkn%>c%upS`bRn> zJ~{kF{=p*u7EW^6zSTs7=Xh}g&pxe16sXmH2>v~8kmyfC;$`0pjHukff$oxuPNLsT zp*nUnrY~0P202sBK6sF8E5)<F$}IoVrL&xa>)RWFDV`5HVn0H=&ed%kgnH)QC~)h) zxCiLEI1Er&M&BL$mr$QJ1V^`U0BhtJS;mLrA8wa-81R@CzrKCbpRJNbd;2LTTaX_c zmNIC%9et{uH^`1Wc+KTFPV+udqrUbJ!FXO|3I5_yTLpX&<7Xd}Rzv&%#cjRBGD~pd z&EmAxfz1X1QB}8pE;Lvb?FQ`R+u=hu7bHcRAk=2+E<*uJrS1;Jz^O}5Ae|*Z4IIkE zg307Wc0!u|ari=Oo>2UC)^Gu~)V^7&YX8dZireA?+usgFfLX@P&rO2t(?0-Erq7S- z#VvNCX!N31;|n*8@Mp(Pf5~xnud+qmxCC6OJOqH9m~H;p;mc1~Uv+rJ>870$5CJQ# z`D3L;vIC%j#lOB{y9-RoWLO*q+n_XG<pSv83m8lAz#ax#+;2++el)-u<`W;Qjb{}L zYFyj5ERPSi|EGps_q>ZN!3mcuxznMy*zOs#N_7;`9|xLh_L|f)#65ntdk<K>5Jk@u ziK9>2RRxUsMjtEM$^Cr9hI?xM`Cj3?QGuCEwU5W>=S!fm_yBmgJeBslg%8h`y4vPe zV?EDrMt8Q5#J-mnZ+`n%K7uc1$f<n)ce3kcXM(_jdt2K;k8!pH$Kij=u}>9F<myxH zy*+(Bs_<2%v(;&!xpJ3TyO?WHSMY%90Ls>6=QY2pp`o=>T<@M8`t&}D73HW~o{FjW zuWq@-tkM3szX5;e8~#pa_`KBZ%vbZvw=<LG3to@^!*)#^z@}^V3EKN-V{@zSMn+qb z3I_hewqwx2o1i7!VP2-CoWOd|f9VQp45G&+AS}2!&%}6!|Jw2MrUr_K^IQezk8~n( zfmDbE!KRNAmsMp{vD17-;4D_<S8296mIW2iqgTsVKpHg3PD?cs0-F0DRsG%&xOV)a zsc~2$NlEX&Ewt0+Dafmvcc)m_qO`6%FB|`#eYcV<17;^B>4x!~EBKv$v7+6Jez&au z{Fhgd<ugJw-zlE;I`T<>AU`Q8e4O+Nq{r~^oWoC{qn=V5f;ol-|NIXp((xn*ERlaX z3q2?GR<9E*OD#W-9P`W~RyJ)N1Wz#Tha{VwH(nMH9gj9(8le@eRmHmG+v#?NL_?M@ z+339cn|Urat6Rp0**aZHv+fxk@)Mr<$^P%l*z8+#GZ+y1H1IiLvp)^w7oR>x=ptJI zRhJ(9H?DYdPwc+*2d9OohYdVJaO(FvtPU~m7+?y%LiVShUbzWZ4-4`+%OZ=AEdQXd zXp`08n{#D<T0|q3k#oL8!GN`piBT?uj>BPi5a`oDH%lV1#ZwpX5%aC_3wR)%B@a|T zG$~<`c?PCGyW)V3DlK~3{mjWr`2C$U_U*q8Z*1+347jfDGIVsj(;XM7^gSAWz(SA5 z`y+i*F>w@%|B@h4Nyj{}qY7!HW+V!lmy+|ZPo}AOoHH1KFa0FGl)*<lu8-K;yi?#C zu0a4(1b|ZCWQ=)7Kk2LT9(C#}iF9916~*qp+Kg%+%AZ@!KqU&oRPJP3R{KtF<$i9m zV5fCDGyt(@cHYTeaUi{!U;DSWCz)<Uk01@#{`>-3D!vNwcN{rtyv~}1J#w;czv>%K z2fSiCw>n^5`+nEEJA@OtM<eD#CUCk;YA+X0yMT++V)sGBWXDxPH=ohm+PiYG-5mx! zsSfEB6tUTRxGr>8|M51Uoxu#tx0kF%>st*JGeFIz>|yQOMZT%atgpXw>vnuxv2&+f z|KGbYqed$`VUx;_tv_M$CHrkU`-qu=0bf*|MRT?C;`H=XhL1qM!Gpb-yBXt?Wg)G( z5e6pu7$0ePz~(Wa4{nCf`i*KMN24Ax;-{2-tV3F+#bs2HyZc$LqUl0H)_JoJv{M56 zapRkxHGMa>{1o;}>}NhlG)$=bEEVi<>B`$K<eJ3`4c_EdJ+l!_qnxx=ShJX*FCcwV zVk-Gt2=WgF`A*6vel;o7hF&nSyLVba`yMm+TwbhpkR``7kmfb%KwRd45)tBC%In$- zV1XoN%;Pkax5#oe5*F8AUx3ZP>;cBJd2NYAou@P+Ysfkmk*7Xfu7EUH8ap3zXi)$4 zFb_~b-yX0=cJdK<bA4UVk~#6hqBu-gko!P==!?8)pL?a;75ZTsGdGqyoM%+m!#jbb zTE)W}-!;$#6x+vdK8F$I8cWoa)cmZm@}SivUXiX}9OpCX1@C?gr%<({7tC!{eUs3> z6B1V+m&O%;G-xjX?JLT{%sL@iJL{wbUaJ-;PE$Xd{Tc*Ba+mSDv;hp9QQW|lB7-yL zvt76d^%vs^o~)zgM})XonuJ77Kh<c47HibFPpN5DH9iAr$29#;l5lw*uhXcCmeS5h zM`Z<VXZaRAnWzSx?S7e51@uzf#a31&Q}b6Monyl&GrT#pJ|K|I@_9w<mon@{`k_s% zy7xe)31+EwhDv`be*;@Ejtn}uaX!ALv~J?WpG3)rjZTVbqDVR~;Tb|FlNuNG`FfU+ zwx&VTW~-^qXdRg40TV>aBUFx;Hxk|_KQ6bnQ}%TdH5k_Ob^CK!T<)R$oqJEw(eS$M zKOEbKg(n|>zCKzpo9>$=ZlC(_e|lU0tGe%uYHEAm3?QI@ph#69DkvQ;DAGX%0YT}# zOO@U`Bq$=iNbe{`AP_>YLI9;VDWQelJ3=53LYc$8zdQ4vPcv)He3&_(vR2MIE7@o7 zXFt#TKKp&cnarT$Qa@xgE9;fUSR`-LGp$2qmOkIUxz$_S!!+?Ur}gs=zK^aq?Bg%H zr*~trmEVuUUZ2(HsRcFm-j3t{-s^vl?yh|{avCl8k{zItzL$B%J=;^w!MU}s;{O0i zVIVUy|F?(ayr+>wSH0`fO&dER1{d_XN*Gx-uYKn{9_5v>`FYx!?!|16Dp3Qnz#8n) z&OZRaXPQxzM{g`{nbpu3+^cS|y_8GoUtNw61wualfDT%6qQD+wL>ZG$kWTbH3l&n5 zz6yhY$gQiSw|IFGuv2r7)II*uq#)@t8yX<eIml)hDj87Ym0TkzJ&4~KjJKvvB(u!@ zNFGG~?Arv4n`UJfoUf?Y@fnCtCSP?+fM&_TE5w2afBSk^N*)1R>h{geFs{w9aukr` z2h#ozf4<y2#rHh;aM3IX?s)WA^e(8A{qd`(Whi|h+i?)}7*`R}f$65_Mt-ea(_os} z534aS?j8qDQmvTijq@@=_$Gbg3hzB!QSv-%Fw^L3)lK#2W#WKJy;tRbzqK1Ac-oUz z)@`q-G??HdL{{vC+!ie?%`V9(u(rbCCVDn176UjXxeFZ``_qXwPuSSLheiiC-B^nQ zJW<Z?&gdvXC>5tJX<xzFeKE+XeLEn>N9z+(6VnT7kDrCT|1BcnK6XYM5}o;F<0<PX zB)0J1SOB*lu$^Fg%@Bne>8tg==QSxZfHie01gK-#;1Q}{V`T@p3%93#%#GPM#m0>x z<*WRQA*x33bSof?S0T=>gLJ_|)NIz`_%6yFLk!n~ItNP76$SF0+Bx%W^V=DQe!DXO z?gMV@=zS>8VJ7txrh-)^8!EQo4`fvjNE88<>$w->z(n^jw^0sG>7?p3-fSNAle`zn zoAzm(>frKSD-75yfWsTmGmk_>j1D>}N(o^xsH`S_%$#WVun`Cnc^}ID0JcKDEAFN# z&DbY}ztE9Klpw^e{H@ykZ89G>r7#^jIqL!n$8T9US+_o}Y>hmrAlf<wC{XVxJJ*Yj z87w@lgGs^>NnbTHak~j>ke^lgJ_;bFRI>tdbZRTkUYTfS^N@>+wVM25;od(!OM#Aa zK)9<g!*K|1d0V%%l85y_S*W%#lEz)Q@XZvVORCCAn1@CT%k+o~(O-HA7whT7K*Y?m zx2MK1|9~X?V{hQ)bVY$nD=&h~S4O2X+lfx^<Q8l2%l(!e;qE{7b|wzxvY%1VlTv{J zhadb1ix6!+QJ;v536aa=Baf{U%Js<)K%f!iZ26y}$?dkZ|N24~ZS~;VfbRN>LA^54 zBVfAyXbFJpYa}N#V!nZOR?80olZxovI(duqu9=l_V69v+iYrku_~nCAZGHWuj0nXC z7i|*2dCeTa7dXlV=w;=M*W0N2C}5nQyZr4UUXpei)GSK4BZqZ^$X&HY*#`XaU+G}8 zkAK@tc8Fy)P!G-!1m0~oD>v>L!u$R02Yd+_NjcKbw{p+8k;G{{bXTdUZhOwqlK(kL z_0|d?_IKZ2C>Jc-F@p?mTr~^%UnHsQHa-o>0*<Gs$0{1O`8x?fW^ZMQ=LTq{VqATC zdbw6HV#tCHq{`ZTC!x)D==~^kAFBgW-MR_^K0H9@DdxLs=v5p<0-}P==i@Mp_rT&6 z(4ap2>U%>hS(!dO$%8f+m6FR&L{#WfaU>V~$Rz7KTPcuM`1_uR2gP|jgaB-2%6$`F z9=Ub*c~qQ};M&?f567Ajnw{)_onZ2}C*G>xt;A5^EKET{K>%TnkCXetKd6!bCHN}n zO+gK$oOWI-a3!8WY^wDEKVv1Z83_xkWXgeBq}dIeo-VK1$u>8Jgh;$_ApmAO$yHjf z<swrZQV%#q1c@SkNCadJnU4B2+5x^GphjQvki|gH6a6>7aQ(VQzb=h<$#ZIezFhgz zoGr23A1p(Z3*wn^IGm5qWGY?e%OdsSgqq}G>^Qay&?}BtLZc>yhYk%?%C!~X?(<dg z$}}KIh18$Y|6`y_G#hNe-i<hGDAF#DlFOxN!^st^715C;YCX6kX!4xEkX$<$^{~XM zxIIseQ|NAW$4?~<=Q(Rzpx*AID@DX3Z}1-f^TSH53Vh7o8ps9GvBX`Q*XKMTD4j1_ zR;GCcgbiG;a!<tAy)?nysjU{``tc4=hq96(nls|SP0N@%tS3kGcMe&=$Vm3L#*(L? zBerCGaY*dEBWhN*^dDoRbD|>j<|7iKI=awCkgCzuBOOlS$Uq>~c;F=i(-*||0T3I& z^?zDg5Y=H@4b=a2Bny5C_#}z0-cX$6uGl4Upj{_RQ065e2zFIsM$3w~0$a5hvlO0b z-Cl2R>6+9f&{O+q$FC^zXEHLA4%@ZhN{y>VUDP^j;?sj?^5;CVGPNGiXsZo~D38$j z)xMuHbK^);2@};^5}T;4ooI_lc(P;$?S+gBkm3VfS5>iR5!}y)gmgo~S5=>87iY^1 zxoG{-OJFJZQwLiIqW97Ne1WW>9C%P>+bNTp-EJbyQffw-Xh7-}b=|pk45KYeY=D>5 zZFpKnY$^`8A<6#ZM}3I2O#3&eU~4(pBv*Nq&aeU<=Nzz3K4PwoY|)C$kyR0$E8rFn zp~i5UTUpwN<ZScrVG(Os0<25V=|htQ|DKzSfH%G9L<!$&W56XIqK?6>Nk<@?DKEPq z90DO^9Yxz>G;9-XE+s45>)cFg(QJl1>|9((N}Wb_rM|W7bDr=E62GvGOy1h6Dwx(d z8%e=`q64uAf>9|&;P)~y2E4WEohHiZFMyFIlNB7=d7KVARtK0@(J7l|m#zCjU)1j0 zE35va<hXD1YN(2=z$T)<VE(3`7_D-y81QMP4X2y=o%hVnB>v-5^|2~!q?$;`$I*q8 zVWUyFGi-{M4%SmTexu<0eg-zkd5WsdM*4cex$5D#&6F9&r3aV1cE1N!lc-OshV!^3 zWXepFD8ce+WK(-H#ohQ@o4I-@wS2;ez=peQm|17y%TR7`JoeOKL9;!EyYq{U^kb!b zlUGH^_L$ddW^2H^3sr>BJ+k8B@_M7~$lN2zQbJoQ6Snl?L0n~fFO*AuK#ezs)+!=c z31}XD0O-=f;L=ybx{&rNT^<Z2*Y^w9=)H)~V$dkBaIbJzD|LI>;1OZd&TG&jtM?wT zaglLvrLjrA@kKu9jAvR+q_5E=hO>EiEY3j#zLFHfHAMw2_tamE;)VxtG=&1MDLk$b zz9=~5(Fo>}V##PA7tb`{Ne(dNRNSj0v+JVO3M{Sz_hf~g7)VD{SgX&9(VWF7^UgAw zvE1TJP_z^p`%nb@#CG6!UU_x*+J2h#5L#yKlO5%^?R{6)hyXGa{5oeP?RJ)J7raQp zBLg*;b8W-YIkJMP`%E4+Iie!8c{F4N9vnu9MhS56h=2IoEyJQaWSvr+aE?Y1tPHK4 zW%N*QNoM$Bc5u$L=*0L7k-={bW3Bp=9=8UBej=;DiYQ1#L*`iPMyM-nPCr6w#4N<A z95^X$Ua`3J&G}@-e0YPwO{$be8N*o*7;j|w%E3J%EV9Je!PU#wY83@((cN|xl*BTJ zkN4LFQG4ZWFPVY7eaKZHsnD0Z1-k_~&Z<1jC`#JUSvboY@fvA6Tsh3Il^FM=)GF1W zEw0qkmEptcs9`_CQEoS#Q<pR)fK^bGb;`Rn?{1;(N%@R++$o}?=<xO<8p}uxHR2P* z9;q63wy=)FcSY2&T`U1u{~o_#zqSqDz_&Pyw|q?jdx`CpRo9tV@O(ABPlK-KaLd0b zA9FmEv&H*L4PNqIacxor6{?8Z3b%ESCdi4jlY(2CU~J%;O{E-uGy|Rb%9-%CyRI(? zIeQ+omlDv8RUioH77xrKM0P@k{VLk@8*;V3*GUYmh=v~6Sq_YdA-bijD02?QLIuZL z^#{a6bfhlZgj&XiJA*h}c)}KS2+7qp>l*yuBo#i;F+|el$8g(qQ+*v3YFXqrA~G}@ zpQB3;($chHmeq<0$w68oWQI=f^)1#5+2(e?`LAIf>kbYo_TJ_kF70mqKf(F=ANQKc z3ic4~!$}&t+J;%$G}&XD3YGvi3ZZyUT(^Euk`?6BU+|`>sC_B?(_0Y$53jIW5HmDR zX+qobW@xKD&CZFUiGItgPg?L)-B*g(1fv9r@!B5FIlnJk#G_{aOj$4I#Gi~+cQX4` zH;zV$HUDE?n~(kiP02-%SSWW%5T-BuQbFB}nP#OVsLkeZxKl>26G+`~;On@yQZz;2 zK_mOlxH{_mXi~L-eO0W$#sq124lY6JdN?><yTAtCe*DwqgeOBvqy4hdi{05(OKY)n zuy|M5dhYlW+qH_=*r?~A$p?Wv72%<g92M80E~74txW6_<_;L^AHJNCMB69=n$&@MX zvvu7adro*mN#P}R#oh*6=B~0=Q$6Y^$!|^@(QxmBFLEzLMa{@)52!Aryy>QreZ1Me zOa26Gb^w#5@D#^9;AEeh<vQjo56{*i1ck~IP=H>K+PVT6aW8PM&q@&TG;(9-gxl1M zV!syE;ILYs)=nQGJ*S>`DBALH0mIvGS?AZU6(&VG!8L<$GrtZcufo)MuYn@;<{Ex{ zH2PI?t;)957K2Or5b%z;Ht`R+Tmo(<qO}Pe*~-+(tr->A6WSUb);hM@+Dp2x<jG6Z z+a%;BxR64~{g^#>kbv$yg^-`U-{nU}<b+Ra1_jNR4)<R><lHmOw<N)eyX4%D!E6HH z5*pks24(f-An`VYaY?D&{2Nmc#|J_NkQptS^BDOBZMb*n5yWkJ{H#cN>0<t{=Z9nZ zC%lIv;tktxYwp=8YN3xQ=VC*64{BXSaaM+NF*utC-lO$Yo4m?9!?x`1evOAy5Gqul zc=gTWKI`f0!2NlX^sCq77=o%KNy31?!tVP?aJ4l&taZ5N=Aly%aZ)<f5F_d$!~Mw! z<x%WTOZg48P8!)|Ee`f-Np&u8-g0wn%9LP$vSK6V+vs)b-3N`V_VDC-Zr7(D2<|fy z{{>>}Uc;!AaPj=rbkM4YYzTOE-aN)_H!~q3Kh(5$>=c9!cNr+C`BZ)G5WEEnIeyhA z;%UG4)QIAMAhcC6l`1DVos0b_Fe8KJhDE}V4BeAt`zN_^ZgE?n<UUey0TS^|6;kT` zJ-eaPHu)qFXi*Jd*(GNDmMBkn#v%U6!Ug<&Q^nX$rm>#O2iq`{l=tZHdiWgcFzgl3 z-`-^61H`Bh$_=^H?lVpuv34{6eOLXo=F1w$?_3NGmY*A2AFrk(Yn4i@nLiF^t-O8^ zk-L5Y7glyQk&R-dW(-OZ2>oOmhV3-Xk9yIQ60+=neZ_(UK`6RGygU$eFDFT7NOJkP zRH#DZAVE^q8fXhW1!N1bt3k3CF82eZs-BP(pE2ZI1i+_XlQMu5zXBSb4VGEaFi><y zc53vAJCwt-yXvTTlKW1<T8BqqzUJOj8<Ws_{*sB?_LX0^x)`>r1VXh-Yn)hKHklkZ z(*-4|>0c&oft0?+ZvXN3jxj5HXf~nlGz1WQ3z`<tt>OtKJ(F?YIUhFI&!r=4HkkbK z)g(H*O?Ynb<qzWztFZ4;ror+Go8vyL6AQPuM<p!`76%CE5GK}2CrtA#C65R4hPcT; zWlMZa!M9Orv4Z;;@?^})su-%IoKI2+pCIgypRJ}4@*?swG7d2>*iM;DhkUyR94zD* zeIC_3`{;1d8$LrHSH@rRE8v0JWyem<vdp|j&Nc&^1BJn6FeNZua)CjhzH2~M`whc; zq~DGelQ1OJt)p}<VqjwmlI>{m2&|_pbGyeJbj^GM#R0ndV@63gfCeSEK(A$AXo7CR zCtmIQaw_jV{hjTiU8_6@pI90mL&#O<)As7eDEB!Kor{?>l@e|5+=ViQaeR+-iL_<Y zRM0=I$)VNW345XV>noMj#TT|)MB@ajVl6Anc^JM(Dn8J25HOkZ789v1HlKX2T|xvh z4^XvBrO!E30%bKrtB#pWZ<i7=P0(vQ8{9ye%BId?8Jb;6emGZ)zr0W4#)ME6BIIHv zDJ8OhBkwMy@J8wrHh3MU>T04ZfV)2=z>u1nLG@vQ?Uj`3CgF2Y7%?dJBicwnr)AgS ztF(C)WJU-+!i{NgpxzzFjT=_?Ng3>qB^BQOwLKm-f~d?<<hK6gK2A^*N!;j`ejwIT zpFvx)`y;igKlM$@VBKvt+iR~Y`TK6O36Q)FUnmJ$K9E_N{}7CKvQG-$T4|6cKOP_N zUW|gUm7^fjjenWzx<{R@+{BIj=wj$;q%2EU%L!Rr4a%#hbiN1Jzup)`JW#!!<i$L% zs%(p|(v(u@4tO9?ZF!oJu+Gra6{61Sz4S>Q1j>5>U~tjN4u^}c-K^U8*?<BaI`3({ zW-)H``r3Q+RLwKU7vboU2)E)9)y(<h9>SPL{#A#2`%Df7OSPwqMrX!`MGS<&m30`? zfdbl+(;OnN)mCQqI)2&3&KtjOm8<l|EJV7vqJ0iJUmdh;RlKWp-(h<qFOI%=_vP%} zyO8#I33<g;nMu(il|o_V`Tl|)(!D8thyK^>PnuYqCIwQ&k>|Mix4wF!lLHJd>od7N zpFmAOfg-?4i47mA1<)g@eBPr`#t~8Caj$lm16%it`}v_m`}Ek}ZJS;<06*A?@g7ND zTZc~VdvoFSru=(iElJJraYW_0%b`OQB=fycnZno3{QjVtkIA7d$<py=(RZfl)s{ky zAHLEO-xZ;*dwcum5UZt8rXfZLI*&JYgW#ibSC&Y7BTeXck9WAFMYWWfc;E6KbO~<E z=%uBt9FUT!Onbxqj|D`{OWv?Nyyw~NWRhS&C;Mx@KRs>mXf9#z+(=m?ouX#+aiwvb zf$_n?gM@fBpOsztrT#k1ymI`1$D~cSKvp;P!Qwvyz;3se=Wv0yb&?7_fx5&BV!jcX zmG#8D$bPxA0j#hLz{OhRy%J>1!=U6+n}gA5p)@!vaAP)IiXov^BYS=S>5T#F#pYXX zCc5RUCy8(7pS_|$V-+tQ1q0rPF+hCN-rRebJjc&I!{uWTefQn3KdCl?b|3mq5~mw= zA>QF{7;fDTx`Y_)L49G<r|RDtnw`qe<}Ff1ey5WfpvUSE{Q$1jWUB!<Kp1R+!~MDV zW%{ZU1X7T@nqMLjTH=4B86mf6$ZWx)Sj!`;;q^q~HWii^eF5)6U|*sm7FOw(^Q#Zm z^AFC}R$7&od*$gG8hzPpmmxD`@``gJCadt+>c-__s-9UZ*3aVlF<*_52BlEN&AS#I ze?l3~-W%-A;b0PfKHF=iOBhWo|9&^ySXQMoe%eAePJpd^_z--^h?R>3zc?K57ZqhZ zN#GDC;bbKNJr^W11u3+1c~)Hk?qN~57qEm@Oi1fW)dm`j9?!7}OR(<QCl^!k`(rS5 z1afSoSkCDHANZ4(5xh<gbTd<8(w}*c1iu=Hkf38GvBI2ErOy~Aj6Q9F(#6ZB{eSNn zBw@>*eWY!m4I3zq6HxG{66GWb3w!a<4D=6>YG%yZe3f1*am>szDDTXNn?%_vaVJ%? zWTv1=PCYtAfFYfur>^AxxWs@``09Ke{MJ@!866(^?rrAud79x9gXo6(Ka#riDwNTz z0s=HE4h}4APpN9MiHs=*;(5>eotaJDdWdlMA00d>Q-MzeyjzudW1JY;s@y~RB@U;` z#ZC+~^!ne#O?N%hAEsqYn9kRXKa-l&=h$X!qg;|4c+cFsZn1ynQtCJo?${?ot(WmE zrG60zl^qCz_X8Lm4OD6dAYqV{Gm%RBjg*2rg`8DK=e(q(7DZeFEwG&Zfp%nhi#$R^ zSG4JOwgBv|*W0asnUf*uM8^LNkYP~^>79xLaj))?9iuWwI)UsEZlAW}x505whVV+U zd~bu%!5AIxRA9I^0*D9mv5ApfE)@F2uKSc#`OJ&8$aOPibhrG=irguvQ{y+xLr*hq z!~iok2-xAS_K+Hkv_Y|!VusU(d+oGK+#rD`qzFuAgVv|`vStuI#~uGv(t;c3m$$Qy z;pGhzC!HwKJ#z~@q8Fy$d~{lybh%pPb1Q6`USMfr{~WC;pkPlC1Lw%7@SE8e++xD* z7X{<9>s73fw2{D^?#(%J-Ksx0pwD~EK3Kw)A;H8PBnu1>Q!3TsTG0E2?>(<C`@Kau zsk=CGLwKzCCavTityye)i5NhvNCpVTTsfWXR`bMfavr=;UzIUceVrV?VHVo|T3q0B z3zEE->A~h4J?MV4mdP9kVAtLr+hdv<pO~n1qO#$xwisN)L`6l-Kqj=H(`08~`Bz!9 z?;N>bhP03XeaPc}{W7)N8CXPFPQPgPp2pg`LhIoUTSr?j(Ig!LL1T`Y=HGG~5pfb! zzSX}!`&>WE(WIb;T+uD!f_~e~5@M6sOP^N|DQPR9=%)nRT9D^ly`7y-ZS(*)w}gwM zV~A@-b-XRHOtiOooGnWZ=@;mFb0`ZN-{%xn<NAu`nwn?Hy?T#K9?#Y|G}_wQ74z71 zGJyUNyaKqGx=WgX!#ZR5+!df)RN*kHRtO%S|8TExfl#TJ^6m4@yZgDzn~W7EPN{+l zj*=>;?n&bG!E&*bsUfO+<4u3#*57<AB9)Lin`%#uLff6<Upy6wK(&xf7^QEnk)9?d z{v4K5@ME4A9H>1UtV#%Vlj_fRANA>~p$p=j@(8SEIIB$qw)GCtmX*G|x+k34g>rBI zhPQP5!~W~c%s+P@*B|_ayLdB+(ySvHes$cnD*5g9Kh%uJ;hIu9DQ_;%lU&(hL(;lF zw?Ky*0f;(J5=6F+r4gyf0n_|)z=In&(?`4HVW+~5wU5AU+0-lT_bq?@2V@_OYAcyn zdhz8Oj5Ah;SJruE<uYS9R|UG2^%mTJ=G0a<L5!T4E*qU^7$SFz$AhYy9Mkie<9RMb zlZFO|YJ?zm&04&lLvL5P9M(FPjLr=6xO(3pE_K+NJvIosjejT-y<g`y9=0qQ{jOCk z726xi;65udHL9Q~wJsnMk3Mx40h_t1LnQ<eAJHAlGWQX~t1`06a;sXVq--mB0#put zAzcA?tNG0;Ukw)5bjEUl6<6{Uj+F^P3LOuA1C&s=6NYY`A(s|w(g@og*V9KvZ-J~= z#9GP)6v7w|TkToDKBv%rLy?4azanT*18ndG>4*OTXO7lV2|cgBT+EzN0mo2ezg<yO z60c+WjE>DGWi(#JoKWIzXUO{*fO_MJ8$<POyhnXx(f@Tl&eue@)#1_E$5z)qa6yzn z-EojW%93|5?6xYSz=VLn#FT_H^L&c`^&Rpd%(vy{GP8ph)R<PqFaJ=%_g@b8s1tWV zpql`M{;JS_dpCvNsfbaRpt1J#IpZ;DfzGN2nr#|^LDb(}ucUQy2)p~D50bflE_Q<_ zKlApm5$S|snRqQHf{3X^?y2!@i;^YQwI`0(fZK+*$=$BKj(jo20&r^6=sS`x88N2q zzf5g_eWA9Mu(zM|wHx#Nv*o<>RL{Z?d(!B)3O;}HJ-@vF%J)3iWQuOHP0NfuWJDwK zPl_Ia0vWF86I*g`Cd4%U{Yp?J!~<`C8UWWBYbf^<$sL*DnmxwXwqA+7bmHiI2fN5S z6IVD^-ZbJa&_#NmgxgXol+F>BF-qj>s?HXxCP$^$XIRl6sW|Yul*mvJy#0$|yHJ{0 z;TIB-nwvKIx9HpFya6f?Z%RnbHTZsWpX$EHIO1>NCuogd6lrHkJ_!!1fv2EPB_Arq z{A4URqWAo2Lcci|K8K{O)LA{8oMx!{oY3sBEZSm>9Kd!C)e4<@z=L0wspRvXG%a@V zd^}s^Xn<}-{X^blJMLRiJp87gb1tB3V;(EG`al%?Qk1c$__?w#K|N;vbK#1XJC$fG z)x+Ukx4=g`qAjO2HMqUvCfxlmP$q?Js-4JIF6qy|Ma=P&@uVwgA7UVd;C(f0vhc^n z#js|GY?F@Edi%$6nwTxd25Fi`i>Xu8c4F9w+mcv|I?OArivQO%z4rF?8ol2h78IUS z46@uhXkn^vfEC#!;zch99NOy<_()a3(_89?yuhc603#_6CoL^n6#qZ8K)0RJ*~8;# z!#u5kl9T5d5K!=Pux(_{3T63%4+3<C{PjbpS8djR(jKJ&?1JS%k;z(=X^4E8#&N8H zxJSD|<1dfE+|?IVKB1Yfb9LyhOZ!|CI1Xlp$w{RMD1-pBNZ@_I{2u{gE91#shcrN{ ze~E`kh010V@#s@c*1rXqqr)}ySu$-SKifMK-7x&ioQo*5Lw86^f$XG!!UjXfQDqEx zvP7yx1c2hWD@jjXouK)4URg*8DdM4$Y<upn)TCYBL>V(uz$Hj!J?bm_4U&n@tvnN| zs)3JlS7x|OGejz9iZoTMLo!F+xpjuEk0<UvFrfCLEW3tpF}^np)pJrj_qjf3Va7j$ z^$bU<l_mY|0I*>ru@z4n^vOIH5Z5UT_WLGf%xfU@f`9CDzA&H1WeOaKvkZ#E*m!&` z^#Hec9TCrs3?d%iy>Y>{Z_N0%=t+8N3VHQIr+D*NS4?YDRCH?;(yiJbzdj=*B*b({ z0ebZJzd_uP@F+W5jr?p1?a-SMldLt1Q8GZ@MI~wT*hz{B)ZRSYigc-?EEE5@6T^DD zJwYH^E$S25Yo$5usy3M9KcLko9e2$PARGXsyDP7{9_)2)GfKDQoRfoWRDlt@Vf#W) zOAA$`QQ+1oJm)fyRVwr$?ym{-KEU45%<{m)Lru!W_l){*2`#Z|tnZf+{)={PW|+pe zGW5Cpc!|1R7|8(KEWqr7Z&R!Vh*n4(zAe0)^mQL8_baJupW~@<ss_B{*>!%a>pxBd z7lG`R!cQ2&i3UiE*+xy6^)`2OGza)8$g4)|`Af|W4-fzT{inyb{-Bk`$!Vm~VU<iM zr`o)XP<rA+yVJU8H|w>IFb1B*n|dGUE1RaeUBS-HnF?l(@X+9TXnL>SP8#V>Qy%k! zrvrE-{}lKqUXbd;ZR)J}XC!MWgatiOY(0^s|HcBc)~Oj+z_;NJ_c)H@*1knOVPsf4 zpP`=;>FJuq9!Hm^|HYwWU{DO3qm;PtLDv`w*}hTYC?wEi5d-pS`ulH8tAA}z*JBRf z)fY%Nop;1<g&dYuHw+n60TZ~CXxc?UmlN}%%2{J2k0mxf{^5s6LEkR3)i?($O;=@C z=J`0BDSOAQE>a~D_iCieNtU=&O~B$jj1mEyZL0ucGO8cMeh_U_8hkx%>F@8y>q}OC zANiqGP5w;NX~Mx_%6!MsHH)#9@`&!|1=WX}Q>Z6Z)sU#!rJLBvGvf^8^jJ;Q`uM9D za}xNBSm(>mnwYrNZ<$RaE&Ph@_ZO1iLn*YKr{c)tsdoN5GosaZH`ZGSQ`&yO?h)9V z5wiYb8Qj31LuzwguT(vQh@jZE0#741y1qqekP3}g!AeW{Sa8$jD1K&B5N#wO!*gQa z7&22AyLmIl5^fe&UeKRe4y<?an<OPM8sfhK$+USvfurGe{fl11FqC!quGaQgu7CrY z4z&Khox0YWPp+IA5~FDH57?<bLr^4*)kLbE5_<3XSeAW?xg-JQ$pDhhfyVf=Ith_C zzv?dGbNQ>ymh$IAz=bs*1pJ1p+-B?z04#wk@C^_#yZT^32yBqRb^i`W;NMx$)aSr+ zfUD~oAi#j@`D0*7TzwF{3Z??r|ErI9sSFhQ`BQf>?ltoS@6t-zk%~+nl)euF`E&dR zN?|8BjAgnRd#~K|D1dQ|1HOByCSNAMUkSOC;<{4{Ce8Tb!GMFRpw1nn+0p6ckB#W; z%T6JqMt8a&q=Zj?_zljAVzT@KHhTIEyjcFST_?6?kv5xX`J+w6o0u)d%8%fl&K8rU z1oKHt<U2zb?=$`edPSe8UX5~9`|s9seu=G5Nl9l(aB=O@N$0h5Hxt9d)nxw5y7`}0 z=ilL(VkkpzY?$A@yO$Sz+|tOEX0VZ$<=g7=mnn^g11C*}LNa`*k7azjvL2_Nf5#Mq zvw<?7{<>GMW-3Q%P5Dko<RL_2fprA(Cjza7b8k}{8I2L&!cOB_rt^EptClh5@>Iz6 zjQG=|4Nf@eUqFHGuiw6HR10e_yN*TLkOrIq1%h2Ka3Zi9MQQPy1If+jN*;b)c>+2n zuAcMx7~GkImn03#!WVWE{VIz|n9#jOj~)N@g#EQlNTjd-ND}k-`AL!!bS3WmvzSpo za?r&4QbuLwWY0u(XDe7K>HKKOdfaJttjTWeX92VOfP9RaIf?OZV!M;{<-t8CxVLb2 zEtQz_ulBY6?V+Y(-!p!w-)gn6PG5=`WOf9xJ=y5hmZFv|cAN)T{Yjrey=GQBvwU#< zmc?71JB(89^RL!2L~Kvy7ZIARK6577x@TiOGk%-$F-*S0Lm_W6y}$aNp@g;jQt{hq zN9lks{h7-&2PzJ}qj^U--|Nh0cNH$)>>rFN2=P%tN*osvU;6COZeAqL)h#T#S;M5? z{HXM8+8oaITwH)Dfq_b*Sn%%d*wj6!-`<#m2hf`cHP@coYa#1|L+M?xT7#FTMKQ;M zB?Z&|$_p36yazaB7rC8<vseADH?CHm+-Px-<)O+C*3LODH@+{>0No^~cutw+f`aZZ ztb`hmj(L*?&t7BwJ8>@Ek3~=DD>m(dlP?A&e4LBkF!MkS9;1(YUXyl}xUEY7K4L)H zfC4H1vPHHoQ8AVOIkMbrcIdq+JXh)Xa|~g86y_9w8$o;Rs(T<dY3Z{?FZ0_;ccr)e zE>G)~_(YD1)=UB%TesRf<zpCz;Yw=+=e|bx_6Ssr^ks~lmC<Fj^HFi3t0wJ-Y%!<r zFo;&uTGZFg%}N!?%~L0%BbBwxAMUtDXc{KZ*V*)P7U=tTn58jsmN;IZ!UAj$kDTCV zGm=!`%P&RJ4cKsQJ}I}!o@%elJ@bWRw8otCMAiNrzGJP~wY{?#Rt$03%0}JeQ5V{* zVQy^pM6owRQ0&4Elg9q-flW7WvW8HdCUZ;g&mfG%Y|bu(&sMXTJsKmBr;{akP0U$E zgfenL{Bm#E{QLNxgC-TM(6Irq{;@Ot`DuqxWj@N9X)G#I${M}~V~|>@`w2O4(qR@d zkMpgBX)33@6DqC*ry>;hpC1)dZ1MrY!NsW8h-$<uWn89vX_5|-<Jg49tBK1as^bm> z^RZ7%CkgUO?kFi@_qNkr4gEUfn$h0)<lcCluCz?oF`$b6^ZF}5Wj0s6lmd=?YIr5_ z1Xu$L9aefz%Ay76pumq`-Y}>g=k<25*|#B$nYo<!$?ml`W!dwIi1O}}gB`Ci8ZdHo z*zFiBz}8QG^WN}fxJstWM1l)_mPcD15f&pSvXu}M$=sB^`A%8v{h??9^ZB-tQg}?U z7!DWgM6>6FR;%@2X^3GGQ=7>?Uq8DOBeuNG7_TXR+@kh#K>ECnJsT=?=DK-fggr`& zp!ngX8+}%d?at@wd(Y^h`NmuE*4c%#xm+%&ktBpZ{j6cI``H%_t;tEG6XJ<Ai>SNv zWink;J`41GM~hn<fjn4@De`n&n^J}?dgP>uUzQau4T@}p0*(J}-u*4#R8(qY8(;AB zgKd|J)cY#iux!t^$WkLQc{$xGM7<vET9Rb|?f~->*<|~f>51aw8MZr6KK7Z%!jjyL z=^qDu-WUC7)VNq%a(!-+DxnL(2~1WFdbh=>B(h@T7n+u*mMl<|dPt?79AC_3Ky&0K z>&WLWXuz8+KOJK<2MCl9Eb|q38E+C>+g-~~a=CDtJGCyDn#Gs^-{U`7la+ot2<Egb zF2W`}?C$hNbeVg58UXi3O;#hjr%mnhchj*bPnhUp><ySOe(b<YtGYzL_5AdZhh@Lf z1>>o83nr)wHWbpqA8vk=uCPQ8AkTJE^SNiW`i=L#m{9~A9S`TCcO{P&-fK~~tx7dI zcOOI<)U05Fzpt&2i9yeS^K*?JYL6V}arcBBr0si7T7tSY(!L@@(LfEqFu&C&{CwKM z=SOo54H|EMO?|bClJG6EpJi1+#DygHP_ZQMN%IYwXmQ9(_SVoUQ?abGBuUgD-38?D z4)xYQ$rCEP*=YG*<+aLDlwYGs#It}DUAVlV%3U@f&v2FNU1zvgYky3cc{Ag6@oD&% z<3vHKAYRH{aI-}{YoRUHx^fr49SlA%z<e&G)~OD@ymZ=iw6VEeqy`^-xdC*8*u(<! zZ6HN#7Oi^}Yt8aRa-rJZp!D)+CPbO>IKOuQ{2S@zp@q_#J1g?qcKOpJ+mwpFJS^lA z=f{t3oZ}DGGbggoHcMAmle+rPg=vqxrxQrB(g*K#zFH$hMrZH#8+cN%{1&b_J)&5D zyr#@?b{rqm^Bdd?Ctvn^awm($zq(}uwXi{U4V~c~>ZMh>9aKmFn!oiP^qf?;$#W#V zR~sy8UeSQ)nQxdmf+gyop-N|t&^6=0ycAwhZQlbAB<oEV8kchMYq^MjHR0FdE;X!t zak=wlgq_dZW215F85~`f&yXkRu8M4TYL1sE@uf$5jxu}wqCvT9eSjO+7EQFjbpmj0 zggu5?)IOHX$;7SF_KXe;7A(w8V?5FcYQd1++^+OrnQ95Tp`pJn(a7v%x$~J<U2&NL z?6o`^F(CPRcmt)^fa>DI(999x>7s3Cm$4~Yel_3pdIaklNB82Jc0Y_D81bswyRpnS z2hNG@$XE7LE&!a-yTTcV^4f13N3}Y%m+8lcy~5M}gQH=jEYB=6PEo(gb2S}&w!mOb z)GDycUEp^^e2+IFz~0U&zdKi}qs3S2+y9ue6Cy(jt8hk5wds9>jh&;H_&1T6QZ^-A zQc$nKp8)jUoj`i+w*tU#+dZ6k-0$;}*anS_tJMZt4QhON=Xbn4o9j3J3U0D{wA;{+ zY{W05lsc%JI`J5x;($yE#1C(B{7g)1>5~A^8IIuqjke}DD*yhid;>L}Jm@58gEN03 zebCk>yw>NZd=#Z+fQN!pO^~$$?-V<?rTtN9Q2@yB%hkQ_K8SnpYTysI(kx_qBYEi= zZYciXCp#IvjQXWy#hX(9uYUqG3!;Rwav@ApGExCf3w~K9Gxw8c_=PM>1s~r`+Ra@4 zzD9OjyOVh7D|PZo>5`4RwwHTrg}joy;EoC-&%a>d@Qte+*xmu{Uuu0^8sK|Wx1Mn) zM#SRfzD_W|dc)#J@x5%nbHtu@@LbAyNWcz^9Ed-tM^ERw6~xDgA9m;+EB0B&KHL#@ zZ?Tqg*(?#im>P~l>}a~RrgqMGjt9*;+1c4?23!p1)bfd7MD|+CbC2;oiS&ICi;JZ} zzLFlTn=HTA0@Z#J{)AZ_(DO?kmm(#w$2jtP>1k2{7o3}q@|fteg)!06gH5~gwNIbz zh-3eB<FPoSA!zfqzZPW9b&Ux%r6pkEuKbNa+-17Zj!)7Hn~MQw92<@_TWPUn`<}hK zp&_;#c&_;fiUC^{D<6F-XSHS#Ki{A?R-)u|{R|WrXts6O1~Y5A_@N1Ln?V)s#fMv5 zAry(o0BvQd-`>h^6Z`NPh7jmNL<P*v^`}jRMJ{v<XmxA15|bV4G#GCO{xTG>jA!p< z-@`)1eahb4<644hGo1Y*T3*%k0s1+&{zvcD@bcDd+u3iUYePWZ(g4`Y8S+P5>OH1R zz;YXtp0pPeUxLszJFp@+ydrOUi5ZYIF+fHD052~dE_at0G}8HWH+i*dh*kXiDR7D_ z_lg<^Qesl^7Jf1dTP4a2yuz8B0A~hLHBSS{egUz~-&Rb3VfiB<C$D79|La2NQwC72 zrNRN|$6QpT^0v3)8i?ykIfmztIgbKnS~~OqIY^Ta&}_7@>aZ_^m2pIYg3B1sZ$y12 zR0NtFz!!-{&eyUZ_4o4vvE8=;_WE?BFNg|t0X@i-WVw=-mIfrw(@rO<IH3fZd~WwC z;y@rGvMXP})iV`%XZ6Kf>XVWr-U302*8!%mn0~|39vltekp_h4SHYr0gVM5(#idu# zi|1sUL?b;~wOKC+L5X32_-*f5$%8fgzUF8@%?=8by?OI(FxE+nuk+y6^OeOrl+P`> z+@S{P4BA@#S&}#V`~8`pf_|xk<F0Os!gRkTXLuf*10qrF)&%F4MfPg#%^D4M1D=f6 zLJ+SV7(dgQ{f!+HP+}C7D2TRr;#z4B5WUed(;7#i_RyWlFNp$Q1pKu-UEc|V4HOOb zWW2)DKesHV0~HzuJAS6Q7#n{mswB2E+f>`o(4ds8j6!J<f+okNr-5jT=1H$<>Eq<e z(E^KTekh6R22V4X_IZ1yLpORjPUmt`)I&IXa3wDu_EC%3(318BNK*{0oed4X)EqLX zn?>+y^Cc%6E-0ijq4@t++_5)oGp!?*G0KnZ^<a-3Yr>J7w*XBK*E#0(=YP3>j}TL% zalGp4zfT)ja<zk{s_ITm<|O&T4z!9TQP?h}D5LF=iKIlg?2&L^hUb>o{YzHJ!GWF& z)3I$I_&v4uC2se6KL%+&1KydQn&B+1;>=5e?Gl_ldNJtM^Uo6lqTtzja6&UkFxUee zXz!v835Jx39E0wQPg6;58iOWf2`KL8>Yf3S0E?^0IdJ{|9~k(*5aDRM1V-fYvh2yV hc3?<Iu2uyRNO&l6LSf72_thx9ex)p1`qK2{e*@GAK^y=8 diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/observations.json b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/observations.json deleted file mode 100644 index 54c3c21275..0000000000 --- a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/observations.json +++ /dev/null @@ -1,151 +0,0 @@ -{ - "origin": "http://localhost:3477", - "appId": "com.example.showcase", - "steps": { - "loginFormRendered": { - "emailInput": true, - "passwordInput": true, - "url": "http://localhost:3477/_console/login?redirect=%2Fhome" - }, - "typedValuesAccepted": { - "email": "admin@objectos.ai", - "passwordLen": 8, - "ok": true - }, - "signIn": { - "method": "typed", - "status": 200 - }, - "postLogin": { - "loginFormGone": true, - "getSession": { - "status": 200, - "ok": true, - "keys": [ - "user", - "session" - ] - }, - "metaApp": { - "status": 200, - "ok": true, - "keys": [ - "type", - "items" - ] - }, - "dataRead": { - "status": 200, - "ok": true, - "keys": [ - "object", - "records", - "total", - "hasMore" - ] - }, - "shellUrl": "http://localhost:3477/_console/home" - }, - "afterReload": { - "metaApp": { - "status": 200, - "ok": true, - "keys": [ - "type", - "items" - ] - }, - "loginFormGone": true, - "url": "http://localhost:3477/_console/home", - "firstAuthedRequestsSeen": [ - { - "url": "/api/v1/meta/app?id=com.example.showcase", - "status": 200 - }, - { - "url": "/api/v1/auth/get-session", - "status": 200 - }, - { - "url": "/api/v1/auth/get-session", - "status": 200 - }, - { - "url": "/api/v1/auth/organization/list", - "status": 200 - }, - { - "url": "/api/v1/meta/object", - "status": 200 - }, - { - "url": "/api/v1/auth/config", - "status": 200 - }, - { - "url": "/api/v1/meta/view", - "status": 200 - }, - { - "url": "/api/v1/auth/organization/get-full-organization", - "status": 200 - } - ] - }, - "afterCookieClear": { - "metaApp": { - "status": 401, - "ok": false, - "keys": [ - "error", - "message" - ] - }, - "dataRead": { - "status": 401, - "ok": false, - "keys": [ - "error", - "message" - ] - }, - "getSession": { - "status": 200, - "ok": true, - "keys": null, - "body": null - } - }, - "expiryReaction": { - "loginFormShown": false, - "url": "http://localhost:3477/_console/home" - }, - "reAuth": { - "signInStatus": 200, - "metaApp": { - "status": 200, - "ok": true, - "keys": [ - "type", - "items" - ] - } - }, - "wrongPassword": { - "via": "typed", - "signInStatus": 401, - "errorVisibleInUI": true, - "sessionCookieSet": false, - "stillOnLoginForm": true, - "authedCallWithoutSession": { - "status": 401, - "ok": false, - "keys": [ - "error", - "message" - ] - }, - "errorSnippet": "Invalid email or password. Please try again." - } - } -} \ No newline at end of file diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/re-login.png b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/re-login.png deleted file mode 100644 index b4b926050b76d1b5108a2f63be54989b44fc55c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74517 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 diff --git a/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/wrong-password.png b/docs/qa/platform-checklist/runs/evidence/2026-08-08-console-login/wrong-password.png deleted file mode 100644 index bddd25b0245e84d0cbc340d5977462130dd9c7a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52191 zcmd43XH-*B*De}CuplDRK|w@%6QoF26i|9^p$JGXq4%bUNLP9XL3#-Sp#=y<dM^nO zdM_bh=$*6iJ>MDQ8{>|9&#!OX{by$+YwuNNS#v(~SqW8DmLtAPdlv)(5x;-;RviQ) z06yXk-NplMuNdTSfItsG@87=G^i12Dxn(eRz|e`?;ohFOjTd(Jo6mf6qeq#5l7@?5 zl>l7=w(*j<)UJrwJ}DuEN$`b+h70dBQQ8xZCv2%ps4JgG8LW7pO&E{7CASB!YQ_!M z8Ur$Sc8)YGEjF+F1c6r4_5a=HZ{BYH_aXlki17NuO9H&-*Y{@j2Z6wVKp^E$pnKPM z$GbN_T;BsVo`CSLKcwN^puWD(lmAb>f=!AmLE48R%vN><159He5Exl4cJg!CxoyCm zlcV}>^13UHmkl@H1LLxk2Ie!}u{AtyM`;oJvfQ{%Slw%(;VHM$$6uWL-r>?e%XL}R ztpqGoRD$8~T=nm{)-POgIw*7H+2Xx=842-0^Y_S;lf7?%n(e!^x8jF|1+_{}G+<si z@|jrGGZ^X<_n||$J)s`nz~l8IGOTFw&bry*ZbiojpVvV5ac013HQRSn`nl{c?hqGg zacb!$ql|)EjmWCI-r4pdcB7y25^<J2c5w30*{>fRtAooF_lLKDKv|ENfc15Fao7sc zPdWC_JDuP&H|jsK!3y(Y(1c-}8^X{hzPZRMzn%`I{+x~^33dW}km@WcAgvl2%u165 zDOLXQHr+w+-rZy!EX+9AZ3~I<=ce3?_l8`ecVKNYAIy8(qV<Xh!5;~$ji~|Q_Rw7Z zxCNS4wu8+nB>pLZj#2Tj{XtR=NE?izCx72h8-I@e$&v=Ct#wq!AuYp}pJ7_LDFNl^ zVFq+%zS=|qF*&&ihX~xkg%#K781&cZqzZ-m(&&D}tH2?JMsRBs#sOt<M6CG0K#l$l z^wv}^LN-VDDsgkU+ZSeR%RT<{(5K(gWU%8t%s7NmCnWRRLtAZ*WsY5Xmg@mh{ih@T z(B4FHTkXO}Hi$aCal78C#U5jy#m2mjRH_uH!4bIGu~HXi;KZ)SsB;r|isAYxp%1p_ zTrL|$@kLu(+0ZkJsPQ90&4-`jiJG>BJ-io&DAA|FoTC+{p>8q)UkE{2=Zx3OPs0Br zFQ47rPD3=#ExdlaMqaCZNrgSn|7RRjE>m5z%wWFq$JXeSwkdYD{`Ie1_T^1^rSl3C zw;&1MmEv(*u}Y$3HlM51M(6xH_vz}mi~zeC$)^PL!mIkOqifZORfGN+#eEf_hdP+% zFb*v)4-&O18(VvHgMGNnz(jRWw*4(+wNe6sl-@w+3ir^0{5+?iNqFm1F2bO1qR&!l zx=Xp|){~njaUt5gm*xxTHnrgpl0TdHHb}@=xqpbm#jUv+mba{zE;zA(<h?T-IU`=% zS#&M@(;&<&9Dz$D;YvO2PD8?#-?p=C1`B2BM}F5A)UVo^&KZ0xVdFyt0y&V0-=YSo zq9#Y6M!f#X`RIB*tv`GI=E$RMpD)~}4s)h@F9&_MOWZtHp0s}_)p?4u=sg$7BXZJN zh4IL}bOm3zud{rWvKqE29lXgSj>%hr7Ip_J_ZKW&MDi`ibn#{8*@4P}L3;lQ;x_ z-YSx^MnWMlwwE-WkW=#k^mIA;JcG~AHbK^RVD_qes2YO(VK{3jh2~$Q577m0MJ8@| zjQZg3B>%3=nj?aB4qjnqge37mfm8St*QWPG7@u>Xot?;M7OHRN<@vcnNely`@uTXm z`P06t9qiJX5nbuaFVpeCY0%L;^)L*D9ito9%Tp2*F(C2nzsMfU_+2)jzAV$6=;7~s zH35PCk-qv{RthGxT_SM+H)2ZMboY00X0Ta=B9@9PjJ^CqLZyc4D(az~ulp7xv#&pO zq}V^?@WA$#Ri(y2$6JP3r{`*b8wLN<FMR&66vd~4?Wpvr-IE^2DTy>cU-GDpBfAo} z6yGCXNd{KR{ug(C#?aKhp=#%Kk!d9`fp3p%pTW45l{393y_wdtcOi#A>mY`AzTfch z-CdG115a$`PFhkT1RS?o*(PZk{~qw}Y<!2lDw2ZZ;3A{`mm0E4hOoS#%qy#1NAPc9 zKt1u%{@xSy4U)!o>7!CZ)wM|nqbw?!@QuI86PmVDOZo?5(_#yyIElxpY@IvjD>*7_ zC6jJXIT*F9E^LJ%Ge5yscl-_pk38-A<MALp#wx<Mz0qhb`@b<OX4zo~96PI!>{Nof zS!>#f23kzOb3i#%Wmx@^05mOIdxsJ<@BaPl!|d3YbmmbiHN<QC{a!+3g0?+{hNPAx zuBt+P#Wv#zoSTw#T(aB7JH~(EFk|KA{h9BwH6G)?C-vgJ(}}u+@xqOlV%}4&hSfI< z?fGzpKe)Gkt5P{15%Ci5*BrbpHFST{?$lI?kdUZ48lAgL09z@A&b+|<{&^JO(NKP7 z+79*0Q79gUUNvy{r1&%YN$+RKKqSV_hK-|E`eQ51Itqj&GBYmE=xtA7$}!Y1uagqq zM44QQdYlNWLIqB=)Wc{(5{_G2p)-4QD7W^QT9=T=74RYxz@8X5704|7ht@gFdlq6) zl5`llveBtHRr3vnC1+65kibPeWqYSt$iX7XngcCz^J%YE;y+E1^qM25aydwTUk6*{ zROmH~9nLCuqhw)b*;O<;TPWggEjCKUYivy&A3}?oDL)GLh^Q%48%B$<Hl1BiL8Gm< z<HergYJ}$usI^pYMj0QVe?dre$FuF7uHc+CSkbD5)c4qu4FQX81r>iUwkdD7?fp<* z$h&eS<I;nqammw@PF~66m$g1`0u0-gV*`>ex*G-Um|(;V=a-2moQeI$A}-RU5ixcR z9vA*In`?&{lJj%*9hL?9pY~JxKCX+bz3zX=6Q*x~*mA7!h(T<aEu|mfp0iM8)((lX zX$EUOxW4b{r$WG=Hv5p<i*e}JXiToo)OS~`TaN14Zc|8{ve5}@DQ4RA^z<2@x#{MW z+Pk#~xAdSe&}lOH;!zvu<HAudpKeX-;>kP%mmN0y5Rayl%O8Z!Tk{TDl{=nRY9;sD zr&b*##cJJ=zQy0>V0LbH4mzbRkVeUhAeiEm%g7!$0CQy@Hv~C9NlYivq(EGGH4C}H zTBNQ-V40<pu5*`W_V__yjIhs&y~e8`O6=}7*4M%*RA?67wztznb1;DkNbgU5cChuS ze;4M9TBZ$}y;vN*MLTqt7UT%Oj*lEj4r59#>gPDekM#G7!nMC3A8_ll6W&+jGWwtg z?>TSR>|G>S-2N$FsmFEE{>Z08)bD8auE?Kpm1oR%VW;vatqDxC4wQyxmrz%FwS%)o zEz>?SHp+Z#Yb$Py6Q)*rBzU9JrnYf$(-85d++D3CO^1OYJ&AjlV1Da3snV-7GH30u zOj2i=EMQU5H~U*RoLKACUIsV53}3wGQX^A_QLVY6;&KP97-fg9G#k&r|6LvEYdWmJ zD`+Emwc$uBeLV+(Pe2T%{V3xI<2kq5oA_smBo%Rn=7M)hW*?j875VVW7?d1u;qOr~ z=#4^4PL<D;1>HX%NRCwIB{S3weos-?#U2rE=<ME)y4_9m!_tHPrH{SF{1`r5c{yG; z$abu5viKuR8a!sz2ov{l32&;4Jh~ILe$*VEIiO$eX1pJ29$i(={W`#O?Z!3%F{sx5 zIwq~T6)at#sRNcY?k<*IHte*$Kp;8WbIcUy{LCX;NTPK7`4o`(h-?q3_NtvRGy30` zvBI|X)~$XWT8QK_#P+y<C)Z^0FgfnnnA5Nn6_mOr)102^FK(HCfl{69q(oe8$22JF z!ogRLkj|3<`tn-?acy*FyW4LmbbGJr*@}ru44I&LVEaEYOne*N;|)9+DHm3r$a#G) zx1}#Z#qNK?@eD$TC#3^o=`#I%Pa^b)JXl_?E;s9ww#;7dPxqCwZh*|wCBnUZ!heOI z*dnS=?Fo!~msK>+o^po={60T{`zF`V{-hRA6!9(*JYr!DO&;wl4yYkqDnkCrS}2kw z?Xm2ji{TB!vqcz5p6H}!mbH8vr#F}g_<CR>eJ4P2vJz9`8$CjSvh|pI6o7s4{fqxn zFUd^ia}!7Ii_IM=qmS@E<HZNx+_j+4l~aM80L#+4(VXCK>1XF)(w3cBjZRuQvO|cG zBqmmbF34C{q|`D8zGIDBNk!RzGxFSdY}pxs$oH`i@Ebb~PHj($o?Ry`*t@@9V>na{ z*c)m`%jc#Q?1msMlBc@^qv3IRzB-mTw_k>=_ko`>&gzRivl-o;+jVHvg2~h(fD}UL z^>wsPNM5HmHaZ&Ps<z?SxQ(>+7#$%$JJdzHYgS<k(C0PgINwp@;y-Aqh#7BoZi`;y z;^i+1MJ-{kk2t2>t7I3s>H6M>5{Pq7R4ejAFdCo2OKuQlQmXh#X#`y5%xzam`m^Dh zjeAL6MwKCVVw0gqL<3TZ+CGOhD#oYS&A_6dSwBBV`*fIWYP3gc{+yrV(mPCKR*t`a z{p6sESk%4S<(J4{l8kfosYx~6>rRK%gWYO9ubi{Uu8XT?eS2E#tLcK8SsUBU344td z^)uh!=E_&!V<5ir`_(eTQ@i$NSf5N<y>OyyB_>Ap@*+4uAhxMKN)rJNQ!{eQ!R15a zMhaJ>&w@1EV%7T9=k_?9TGd9@`lOB?EM>9JI=tjgrl^VE!;z#rn|x%NNNIAgaEg?F z=keOz5V(YK>9eUaK5YTJr>#unv{XMeDTgWbu^RBba-SV5+K=|ic0ILmaX;@DXt8FG zJ~pHGF-ONa85SG7|4P$I>3`*;`4x!4w$FI!iO2I;4Z{?MQ9A-#rMyz!jXJRD%HfV< z;ynxd8X+lnDAICk#!dXo-`f~*=XbV<xJtEQOAW<pb1$UBc;WaA6YK0$p+AKOY)H%6 z>8QL+D{@WwZF+s&S=X~G^ueZp{fx_pLD^5DKkt{!WoO_FTs1YNjm&j)iU*V6m}ByQ zCAaw0YFpZ^GWFv2l`nrHI8#Zd8%Dn+N~Ro2hrAld1w0c&Sf64ekSpDbJsJD>aB?zi zMvTIxNlU-X=%~2dz+=wBHn%2aF=L@e8(Y%tcI2)HqU6>G!)xp-xQ>Tig*u+zq7Z`G zAJ^u`D}nE(4`i#)%$v@S#8%*PZ!{vr8R4IIE-YrMGTNI8UjQQFQJ%sBIpFgLbHpbM zJ!d2<9M(PdOLYn9{dR^s%j<b<`qxnlu8cabKY;A<y6n;8*@2Br_Ieg7#`JQc+}y6T z_bE^z^w@KkRWw=M4Ep=3U~L;Y2vl>OK&ztkcspUUc>12+UHWC_y|QW(FtkZh=YL@V z@Tj)I&d|X;YN|f9E^o-W*NG1Kal;&D%^s<S@((UOJcE9m|ALIZPkp>M+0bO;5wW2i z+iP<JR7>G`?;&7W-@!L1X{m;rD)n;Mv;1xA^vNhG&m6-SebZK2>*yqQgpcZtrvzwW zxwgg_B;v|+X{{J9<tSAgd(hGX029-94e)_1o4Cu-Mu;fr^{;&P1YR{eeJIyow4rU2 zAUKsr3<&l0@pmH2&R|M&Ihl(CKIHTnl#zMsVjpe*-j3v7G~TRzBSA~ARM~ag;ZBi$ zGYr1i^CSfRX0G-S^Ae(KKb#vaojP@yyiWvrb@Jxg7YKdiadY~8tofi#Hr3Df(Ozk3 znQbbEN9|;So6M{L<(Ys^rl)Ivg9WMT`v~g%{oU#^>9X5EekFCCcVk=pIk`$v4|U() zL(FB5>fLSF9#?NbLtB3yVm6p%l~hk}=~9(l9ytU6Fv6wQzZs@aWZWqk=d9qDk^XGD zVBoZQEY-MOV^m8mpub|=y|`&Z+<euIg@QoMh<~_4e^Ni{URrEU*TBO<Ytt*9cC^%6 z8+oJ1@r6Yr@ayBAznu8NG805C6@RC45d7INSeq8BK^0~0i1z_6pi_ze0>p@enAz!6 z>9u8DGe?WOcXhuen%>*ZeE1Q+V2bjenI<n44HYtslce}q1N&=>z`j^92GUy2f64I2 z-e#7ZE<Uz>?;q*h#$jZge};n+zU)0^Qu>vv@4T;Li{RmDozd|0{1(`gZDc0XUw=2f zSSCMqSa_#QXZnx^6zCj%jh`4!Dzvb~KBp!p40_&`QvRAcveoV(OQm$up1uS)s8qcm z4!}eTkgYOI0zA?QzJe*m!->~os=H2Sr!(vd4|<qBbMvE8T>0;Deted|J2e_zMmW!@ zfo9R}z8_M=yFmq@G1utQ^lVvZOTzTj2fZ&6r$#SwyTV(dfTfrhzg~*n<^&7w@jRo> zSzo=xbs|t8*;62l>LF3*0T%0j9BBezt^bC1Ir6hKKE-}{DyY96`-SmMlFLodON!j^ zAy3_^rq<$w_R6oS)&!uJ#4;9nMxekK&p)#9I8a%42HTp4^SrWZAgA>R|GXCnN(*l_ zt?Jr&^QLHs^;?7sirV0J{7@|s$Po|FTICk~gUGYdMZ>{GLof(bruyrV#;-?$w+<c{ z2@-(fJb)%EnNLl^-_4qpjcF|`a)UtQ70bWac+Z*7Q}oWkL?E{DI6P2KO>ap2dmpaX z67LZp)h8zhR)xMHyNeF(xk12>f_TpZow+X}cBgGLKFIxaCj$kxT1_i`dQ84H{%wC0 zXu%7#*i}q!7+=(qTR*=E%Cf2tYlWBbdu0Mb3J2ZGI_LkW%Ri{kF-8RnG%Y_KqoVoU z`ke=ql?Dimt%e+!wOsl9YdMgrIAt(;(MaisyEN#Phzy8O)j#(UMcHdSkgBi4>#(sU zpfmRC;VAq_w;Fa_vjnl-2v;EZ;bwH*y!RDoC2^8XQy<7AB;&&mdddFNdbii~;oW!; zh&)r;{$lrLpmW&&13cL*7`HZFl+&d7OSan~TnE^AajQKu03-x*2$&|I1(m61oQ;}e zbj?S2f*G}%K8C=KrZcZm*cvIowhWkU5$%tT=jtDwIHl&1S<sJ%PxY*n)~s0I14a`B z^ryo<qD=1c;dM-c%KDNT42E6N3Hb09!I}wnd@c!o`D1I(MfNb%lX2JiHIthWj810k zZbPV?21T7NfCHH>(tZH3$>`f(e9CN^V0!zjJyW963{yR4^a$=a8H-S+Lv?{)jc4uX zRGtD@ap2W$z=PKIEb8SX;S=TT-M1cQ$$pcl93&T+!S8pPV6K7+O*=VHV*}G1@BTy% zMn0m@&Vk8AN*c2CO(kVfNe66ovbKpfHj1`1Mf?gTBm5ev=hPt@eL2A}`gMkHNF^aI zHH!9d`|AsWvDnc=-lS=`hKHVe*@$;*Edxo%6d`K~J*s{%kcwrkf7ZM84}n_s-OZKK zSf&)^<MqzyTdteimNPMv#byV%ADk>vkMwh8kG}n2;6;qci-;}E_ffID9otz~gZW(e z%w~`!Cy41Hu4dFsE+jKVu<=4lVm*G=FFNXP+7kPYB;hczIjf2VHrzqc-rTz>r~x2U zCIU-u^YEq%mCOxsR7;;ehVY8}mj$-7JWAW!=4=!@z*TM^B%N8vif$b=n~YMbG+2I! zpT;+xv&9fTGQOCmp29Iqn7391hd6t%o}F(dLtU-YeL!MLo(>z<n|*9&l9x_1bMR=f z`;+CEW4X$!hoHcx07f~_0VqxTBV9#FLD5L7rHZ9!d;S}i3@IKvT^ni#As6K~?V(y% zbUzIx9Qyhq+0oyHBxrMGc_I{hQdza#!xJu;q+L(Vh>RW|n|_=xSgU?p_AO12%X5&m zZFb^8KtJvNnPn5&XM_;@y@o`FCs`O}wiHhGRY@#~YGJ?IHbI6!O7z5Wxxu*8yTl*Y zjUyVEdg$tIx?GiPDg9OaV1rJ=^~Z}HhP|<i9s417dWn;E`cEOND!w~AFPyXEy2d-i zg(ZF;q`ItTeoMQwR=KFU163A_OSC3CEYXb{vgqMexPP&0)jKgsHaA7ddw!~veqe4n zhi1yplz?+zY(+z4$dS`^S<K(ICIq2~qZ;?FJ@EZfNx0kUkr@*njDSlBy!`Q2m6!Et z3E7XmRVACG=PtW03VZHK>w{3E2Q=aqy9r%hbJ5OvmmSM`byIc{mltzJIoLB~5rt)q z|GD;B&m^kleXp^v^E4iI{wS{(zjn7BrKXXY>AAvo!(2NDtwmWj3wQ721Mi@*YT>9Y zd-ePx!0tm?hCq(-BI{9ULN65d_}+;|e=o{d97;YOtBNDwj6yzwIjhf!ii)&Cw!(9S z4>KiMopnQmNQ0R6cX-68=ZZD59#~eF%`(y|h8A#D<oKWAKFmvKuteKfr88yiD8y#h zOTtXe?DDq{R{+SeQ9#|*-+4Dlh*6Z3|FE#;X=enu{It32>*JCfLz}t1bp;RPA?Z`c zWewg^hCOse*YTxaY5#8+<Hc;i*{D#4v@3)$8{YJTT+(tkZhBUBu`iI|mYKTudUe?` z_wNPpsN7L8i)F#I*>a90N+k&<;H?;>Uoi)l&3_+#h<)%x46Z_5VS4hp>jitdysIDN zlY&)T%Cn&T`EP_k9<?x2ZVdZc2ltwBBK}4#CIp4IeZK>tk8Ze1>^jcydsBiht{+8v zW(8IC8j{|oL7TP2rH91G;76wuC2alO5!;D0ktng<$2@;l6(k6OCc(7!eRjI#;GX)H zSi${LGP<u96$^@2CUp=pRarM%EifCG!j<R~xQnwQr|{#ZVq4Fe1Q-D*>m9H+)qahD zi_|Q(AWrvRunw@=rH_=Xl85}HpCNOpaElk)hp^3pxnr>(uJ(!(ACJMjnu7Y$?yMbU zD$R=c*|l%ncd?@TlP_AusVsqz8qOUytT3<xAfIfPH#?<kOByS2yDMs9*%i_D5d$^u zeo_~w1)hz9AWQ>C&0mk-+{5`c<`CZK1T6{s#!r_`^<%$!z5UKZNBJyCX6Et^F79JU z4prnyQJ|88yLa|`rdZMuA<CH<*@K4GzbR^O8X{31+Nl@k5@tF_5$g|z%gxuK9v4wm z4Q>5;+-sqdesa{&wx(hCCWU3i%}N5l$a}NQ^_I;6U!gTn_~;82*hwLc#49uGn=DAG zv_wU0^7T4dMSi{G{Td`oGhS-`I8vS&(_G=>7|rrH6-f>XqysSE=F+%y|5dw`za)3| zsc`y<ff>-URP$nWBc^BB5F%W6Yv~$X&h7&H-7=(*7{4^g&{aSBKBUipUr4~168^}` z9wB7xp}fHDY4|8_%;?4V0iQ<kK^gk!%p7`nyGvM_f!Bd%2dy%A{(I$=xVVPi;DyUB z2GU3WVlP%@YI!cu%*E)j(CE^}(X4;v>v!)0doMjY?@xQX?F)t|_?WAHS}@>r@hpAs zl82yJU<c}xUY&x?AV(rY$=@d1sI-Je{c)7nSlL3NvlZrY;&Wax%F&VTx7p;^$YRvi zQ5ZGNg5~3NtvWS9Cp*98<flCE;zy6eU;MD#4a<DWZGoX_40!jgw4z8QI!rv%L&DMB zd2HdCJ#46XaW-0?PB*8*W3dIvI`S5`@ns;T_-ZoNzme0U)^-BCa|Kx`+K6QufEc)~ zI$yTyA1P1`dpIV3b;A>Pa~@ZIW<K{J9km3NqOX%$NckywN&Kd2P1r%AO$JemEtzpr zOb9r0E#Nhd8V9OTVL(_KI1-ocY_W#wimZboi6ol>`u5F8%~+#LO35-JWh0w3&M(fx zM-${S1`}k)-heHYQ)~EmqfcO43=3o;XD7*OpVEE4Oz{`-xc>b@FA2_<I++c7{2NdV z>rTMNtyW>epHWD9@oJc^;026<Y4BOv-0u&kF1;iA5wsIec~@57a;F|{j}#n#8KqnE zJ>S+I^k)Kxkoy?pO+}=Pu~?&sASz%gicI@Prv2UIO6c_M`sehYv?O>kUy`|0e&Rnn z-i)>=n@#eOTkmDP?+`)pNl?P|gbG9YP)+3Qxcaek#nxVNzOui))@~$!jb#pFo9M4H z&&x=!;%HXGgj*k5OfgNGwh|i|-wUi`Sud%*xZ3NhA{EyBsszHsBlsN-P&t6jKhIL| zgt{NBV9IzRKVCazAc}hi#7c2b8!!LH;Br{@n>tVH1JKFn*gQyj^3j72hA{ff0WY6& zq1}@o_?+6<Df8BSnGokK7ed*6?0n{<Slzl#LzwRtK3u%+<LX3UTjeeHZMEMmLq5jF z4KEn4INoe~`_Iym^1aBkeZc29KUN^it9)WJ7SA$@?(vAi#_`+RMSe5Ex5zv4+`$UN zk3$zngQL=gE{X?4T#O!pb1X2l7py9VZf){~mf?)};12(hYKt4uuQ~FPav6>sM!R!t zFhvsinbvz_%RVLfBxQyrRsy?qDq@)d7a5;SNOLlzNO2=w`Zt8@&vkVwcM@HUuo?BH z27^snOkWQ&PCXnZrz=-}u`3waQNP%WuO=2ffhGT)?z88MB5wc0Z#_6%8B{O4fi8+F ztpOLKJv?k1mYS`Ba5m=Yi@i)iP5i(Ld9S}rE)$Kd*33YR6;OXf_X4r06*ENCyuq$= zY3FcU1qxh)qMQE<3)uaZ3Z&G)_1{YGs>%fWNj0S*!1v7*fFlO3|C<_pE|9(1Y&gvg z>PjxX`g*(;L6f@58(=15P{bx7!!?>z?|(QVeEF`AoH^FZd5w+%G7)F`&|LmBSmZ@Q zKs75;^|KehJFos_$H>IkNOH;k^q;2bFcK0VhNj?X2B~_F#8yK@8ze>fc-l(bkM0p4 zLdcEs4U^!qSas%R)?P#AOA%Pl_+Wk*yi5Sm^f)n@{;c1_N2aEX1nI%s^6+DYez`@Y zNAcRJwlf)9u|s>vEdWfodvzP{6+54i5yEbC@mZ#OKb^;si-%U~$RgQNrHewA=oNj? zxy;58a%c8u6Psa;X);@oXU+}KKok(Q=eB*1sgg1p+nV0r&NHH&$|~pZS{hkuy5i$6 z_8XULHP=IuGq`=YM8|e&^p!Pz;%;-3b2oGl{AC;c%E`Mwg@*OQXMA9yidFJ8^iBai z3ZZi$J3EewG%s}<HntUi%5qS;82?br?9gbqq0DlBxXZ<RVf*p0<l=Uo|2XP{OZJuS zk)QJf*&7>zA|;%du%eMqo%URYS=)?YM{jWe`T@<Dzq;j&`xHOCQ2VlW2CeQh*~6M} zXTiwrykNTa7ox#yWA;Fe-MY2Z!t>(i%o}{@$vBhG(TE_C={;#X?}fmvgDrs~1qjAb z!+Gq}pnJMVhN&a*?af(<Y`OA*YN8O2ZEzFjGfa6KJ5}WO7U7tA?q+7zajA5s&G244 z*3^Ch18&4kz-_dU6ns$j%fFx6Q%!SX4`fThjek4di0a^aXS({1N5gd<cS%TQ9JIc9 zr1Mmc+t}wNKg$b++&j06UsPF^>eJWBiAh*rpu3Q$3*^@^sFs3p7a}28MSCbFSo~PS zqb`yfS^p!%z9?cQ{8B#)dNuu>)<3FOMP<5*+uNtgZ4S9TdypbbgYKA9AcyfLvwA4& zrBDvfuI-nNc4}y9*#sY8jPh-~w<?G`pPE%PetvUyU^m^TqP2Uiek;TPswN*ITfC=E zmu(gk+t!FjbmgohQ3vT58O>PW&ahpAzK`)*b(F5o_p{S>-rA1%`bJLf*n*YEm{cSn zQe{2M#z>|FSs+z$B#m&eH@-jeX9vC4cKQw)sC-de`%-b?A@wSIHrb%X*xmgBNxE1Q z^3_UKUdwK_j32JzG8(y!a9M*obsZh+^#bj}D5b2(!aJx(J_7tD^3mHSUtKIwTajrD z>u292me&SQTV>mN<3gVX#M$qiF89OVHhL4wdFEs1+?`Lt@U>)N1<>A!M$@*>o|)Sc zVv)onx$SRxr^?OCeqUUSbE(qj;)jz^qYtqAs^3!?nQ_M=Kh?ma5XQZF)}6`S$<r<G zbg<&xJtw8QatpuZ2(4AtQ|(wjNXuu#GZ-Ti{cRG#4sK(o=ln8bRrV&+!!$(Mf(r{~ zKH7K56PbjuXa27bFWOSa)bkzme_P{329-T#hM80uPC5t7`Mk%y#~DtfSvl>xf0{6A z2|bPyPn;>JK6o2A4Sl@!)@n`OXxv}_9!u=7METK7WO04!fGmS?o%NJ&blFia8D;QJ zxEl{<SUXtQT3kB4!g}g=@*Y|7x`37~vJUROduY!}Re~daOunhpN221sc)laFP2zCS z(AN!=P@IDSNBD{zS1<;5(Hbu>-0`j5kA&(3RPQ$*)^1@ORO9>(yMi@$%3r4!YaO5n zz9Q@4b<t+$YCg9Pw>5JTGU3MYHZyI05${8BlQK{(ni25^7keow66k5aEo#)flbL$A zbPUO>+ELE*UgYb371R47?}bk(zy2)SX=wYJHvBDF%O?}gzF*>VWk{NyidrA8^xFBL z<*TTlz(yDk_8hD}@+ccFvW_K{C_b?;j_Q3*qt0U&MY>QyYd2$YG<3Rffw5L*c$sMD znl>62|5MmX;!TimgDa2!CoO%!(x-#$<f0IF^r1v|*#haCRP7i!qza|@ln#G}xZkt( zIC78Ech#0~qsUZ(&GVwMTDVJ=r4f2CRLqg31-d;n@Dq0KYUCd;>QT&);^g3>Sk1`n z?-RlP%kAwI+Q?}366=kG=5l{nyPH`u#BAHZDRVP9hveDnY*0^}_imr{!Z`ngp$j%< z<zxEPSOLTOHuKzQ9`P-ti-_O6bdef)6YpWpjLpYX+ERT1`bo6TGfN%pw2f6NEq3bd zJ$o#-dJI&eRVdikb1bF#2}0GFhk_6k_vRc!2+EbApq@RA_ZE5ozPnSULzZ4d;;3^S zsf3msE*?Shr|_|gw9jv@t)(W4YGr(qG}?T2<{62a!St}{kVK;6V$Hdn(`_OX;XJ&r z3*9m}Tt?h}f|sy5gEJ4<HHwA!29`%f#Gmu8D!PU-3b3@#Kh{3zII36oI`Q&GU)HI@ zkG*Z*dRMo2E(N?2usKrQX;2!iJ@X7({@B=^rSg8DOT1~m=5bW4_gQz~nyQe_384<L z0m8DwBN3x@L|m#~s8%ZfNz|l^?c;1j)iL>-)-pGhoqCJ6MfXnbOcIjJc{h16J3+HJ zes1GX`wjwsSS^7CJQ@f&dt{-8>g(zr$;~aCVfs8H;05019HdTbblAB^6RfCx97Aja z^*->^DNF1YFNpW)_`#HjY$1g;K*3dfVyDdu@nTELteCx7-?=`nWH+Lz#B(|D$|iQT zNGo_%Fe9;16ecN#Ijt!vCy7Sq?-{0x0{opG#_RBm16HD|9Rt<2cg`L!IXi!~|3fHV z->@;FM!?~wA9p{@c$r|?90s=f?x3V-S|udXE5;_!%!|C3z$N`DahWuF-PE*+w^o<1 zxoMHOl^KbbkMee>9Wz%gS0LT9KDQCy%^JTfAnF`sh8Vl}o}6Zpwt<7GgdpD27BK!_ zZsV-zV*{{~V7dgmK(PH+#ji-F3_9{n59~xA^^Ym>o~B-z2J>Jg?Y}Y1dg=Eme3;BC z+JY8qI&EzZ4iIILzc;o9xL<f!*B{x52@H*ngDGk(`Xi`ghuOx^PY;>L1?}&|wV#yF z9Jk>EWdmt4D^RVBrqL`dZ1{UTc1T&P^asTlmmbrA?S!3tI3mrv*DU0K|AU+Q)+$qB z89#1w+J?Y-F*;2$=2zjv1jI$@xkn3anEd6jQDo04Ne{7y%o3K4jXx?=v9|#iciFH1 zg`}}~bdN-+)LIeEkZHKyAbP|x`*cBb(XG^X1w3CipsY~4`*N`_?pMQ^bFrWnzU$X7 zF8MEZcZxb@rgT1b7<z@gbw_W+;nBmu>mP%8Cwrz4TIDbOo*Z_4nz)%B(3^#s%A{Ky zG%pB}1|tuN_T7i6E0<!OF<6-`K%#6y*Al(|Mt<u&_Xmc=_@(d2DH^fXd~V+@3z-|T zby(VZi2kMjp(j}eV>X!gu`^S=!4tfTP<5{~BD7N!$;i4(d^mQSYVLzdTitOj9xo`t zWMt+ML#9_-pf|B?xktBreVTU=XM%YN@h1H?4Z>yob2`=HsM6Q1h;@9q#K*P1*iFUv zYI2F#y6-+0mFyz2EMO{Q5~ZJeMSl9QvQ=7-<1<0lYz^mo9cjn0tO@p!k-~SO^X0PJ zi8@e;N8I@;q2RhhMQI}o*TRu(WeFz-To^uliJw0*Z|u1C0258G%x8Vh0VC3og`jIB zd8WSmd7XXr`@v3tefKk#qW<F)j<|Gfht|Umm(g2>EDrW2FQ0FI;$bE|Z7tHsXLfap z4OgMD%lzJYlv_o^3pK7^NG*NOd<HNG9mTTl2mp(R(aLIhFLt;Mew>(KQ6YpXrJ8kB z#|)_^^Nm84SG995FFcZ0p)(@pP`%o-8X}V2nb=a&nuLxi-Z7O&D!4+L5BB0)w7P{w zhjvxQxp+JvZ68?y;^_v{HX`^V`XE;`o}Wr>(o|ciyWOutI-gL8o0kqerpHN^+l?Q3 zplH8n)6HWk(Agtya!+1(kG2RF+s!x}h{D!?Ol{Sbd(GI$iA#35;7;3KSZ;NzcMEJ& zQfrT&@}|k<`g_PwYd=mBVM1r2kVGUzgD=zLJrt$o?&*{oxOn1JI!6`dRUjU7fmySt zj@j-0dYBTJuI@B{F7b}m2W+`}OFjvCIa9DQ;A~18K1<trr_KCWL=`qcUJpJ}*Rmh> zeb<rEUKx;TOJ4Sxv9XPqbIGL>aP+x-Y{f(G^O2KP-vA#j^{?z2xclxuc}uq9i}syK z0k>3(KLyp1;x|EY1U<1*sdSRupe*rhynCQ49<z=GiM5K)c(x~nF*(uP`kBl6a4oj$ zlF&Q~;JG#<`jcF}x6yueSOpm4LS(0V@gg&jz&Q3^L$GtX?vSzK&M~8FX#R>H(0<_h ze<votD&)HpskUH2Du42@ww=6{%h*OCPo7#{V9EYSXl6eH2p)WoLxGK`?U{iyxsk<B zmWxZVWv2u=FujY{a=6qu_E>0U`nnkg$4Lytk!lMZe~sDpyDV9GP7jYD_bth77pHDy z@&R?je-1V(MvHt@<zgQh<A&?V&g+gc7O_1n=~583IV_3}H8?_+oG=XNzr;khtyK^u z_as%@E}TQ%DJ!aaJ1zW#0`$_1;*$6noslGBycc5jF0mv~@2g^jiUB-2`0+{Kui7Q= zL0K)=_T<_p9WLNR-(w6;Ky6Wp<NLxcjv;UGa=|gr@1&dc@cb{*V-RsPbGBFWNS>ck z@-K@JJph&l+6Lapa*gwUIeY@Eugo8w(a;!s+p*jR*6uhypdV(|&6lcix#%gCY=hs7 zuT@=FE1i2H<(8!#m$acStzVt_{+v@h2z;L*>kz>*Lg~J&bZ0nJugOGkMyoxkHcoiM z`)hB$cG<+kcpe+$$IhlFDWeqi_dJ>3`8llBGY336WIz{4%a6qf{(e1k>)svB&MgBo zI_j(@EjkiVVC6p$?1Q41(}$Dlf+X^sA#FQk<D0|>!Xg6Lz6^qZNAJiTD9*)+S=96r zR6NR3{3d?&)~0P!=^MwF^S0!A&1E#c`SW{jHNfYI!fh?x!Pg|Rn&lQB^aL^xI#jF4 zp8S%GZyuA+y1z@b$=FyMXL!(`Vq3$Vg3vwIe4dwE7~T2n7E;Z48fLL{nxt5FI#2nS zu=;kDtZ6JCr^C)x7nxdjQO|)s@rIVvJJyl=&{M~ajp@@|75FgzsZMu)^CW6={7;vD zu28L8$S2aeN=suj%;KV_?ari4X8B(WeE8VDRzB9S_V3#IBSy-`3fbF7L&GG8cJn`k zeVSl&BWA*-la;*eas`H;ZG^Ei*(sJLS2w#Sg&pQxcb%)>8#0!3H*dRr#1nXiy|O`} zbZM#@7oy<*g#|QXB+fr{(hA>A+kN~ks{0V|i&}rATnS3wQS)Li%&-isN}`ZxkYUvK zP9_QWmUC>P>r=+}V})xT?4m;41;}+VjOIJ`53v;kb|*GU7U>+PW|lD{ijt9Vj}5~q zLHn!I%)=y`s`a7STBe1~O~cJ9YvhxX&*Jx6V4R!T4<A;_5G<gxl!`k9Il7oA4a8LM zH^gz{0X_i4Mv<mK7zlnMW-^n4Z*4NeA}Q|ask?fp)JYcMJYVN#d%qZ93VItL{I&SW zM(wpUH1w-gqTwlLot=5(JL1o{)^s)16NXTAF8j-tAtd+ULeDmNJJ&Jy5RgAFJhT+V zD%OU~&9v3Gn0H<+{V?04^ph3VVh#)Jm=y`7q3J|HjUgph^KDES^s|bM0T;Vu)fP1( zp}0IkkNbZ9uElrP+)JY>s<8(pb9?<%ol5=<DwQCgT+INx3$!4OpsbYmV(@UO>#gdN z8lgy3=0vT#pX~Z~z8oX!;Xen<3Cw4_G+!)ZTY`29SIAI)Ls!ev8C^;U)Fjjx&WCF* z^Q+HW>bU3c{5Ax=k-sar?e;-23f;8(3mpCP&B|5=>rt7M*^FnTaZYCj-BHJ1@Az<? zT%l*o+k=h}@+~9Ji-u+jJ<iR5X9P2Qe~09DilEB!y**;jlCzCM7!MRX2Tu0s#~P#? z#Ctz**~Lb%;}SoMNs2nGCs8i`MJ<KO!XP=@(KF+f9`+2KH5yAAjcz$v(~KPoe7H&a z8x;{HxLADqHKGCRtalGJHSOC|0KDH<y#a{p71db4v=ul%pgj6?c|{<n;QK=cvyutP z3H^Zss1B>upvlzD4F6A;vkn>C4!aNnDJoJZ?|#pRc(7w5$oAuI@uOdnh&U5Pg&tRA zeTHJ;1jodv;6SeQhYx{5LM#WS(rN4^E(OrE5!M2|a+~+A>e}C;xkOIK??bfPHJEIw zNW{vQr&mg3GYA9{29!l9SzATN@5NO5+!VOa&n=ZUHmR}|{-d|VxKkysQM9cS$?iH= z8GGLlaY!4Fw9#@uFMQ)9NnE69HIifYvyzC0>Z}RPRx`RzwPi1!V9MWB%O?LP?vV>m zzeGV^X3-bU9+3ckL#k<UT?;is)yn6CvDM)|Fo%{;85D|T88jL6+xjZ$?C`(6xybsk z$e>c<an74sV_215{@Hnksgl~6-lK0Fei#Gm%@QeGXDjsRSFxWh2?OosHBw&q&#`^? zGF?lE7c(={a4zwiJy>SQQIlniXF2lG$#KVNu9&|_zPER&FQzHOqrFm8t&~+QIV8uU z0Z|Vx;VM-jfbl6noDcRi^NG@*Vq(;^UVE5RlEm}z@M`utOudTKW>&xZ9xdzV-Lc|F z<Uj3a9$2QGH_TQ6<*9tv(YJ+%D!Xrm&eb_DVy+B0m03}PTNnEq+7OcOs(feMINlY6 zlcd+BfthrIkuSnx_$}UM_WiwgNW!&iT!*Lqkz7wUM?cXc&H%=<ISneI<Vbf@Lmj<~ z_1O=v-$+cu*hF{hF0Wd)W0M;NPzayFZN@{<ifUOX>6Ptc1Y{~<MZshJYjvqwTHAdP zDD3+9Q10M^0J{}b6|Dzx0N2}8H`AG_1JoxQSh4;u8t0|&ElIH!{92S(N&2!v;8uQs zd#0hIW^SlmS6_!2%PVO(dze}=qGUr-ip5A`<l(kKv(H@7pRb5jZ6&5J^k<Me+YdtW z8YkBtC9qe-Eb<ome)hd<6IJ5Q>4~)&WbwG~;G8e$&ZAdr9-3)93+q2Im$jd!&$Yi? z|0UYAIFfgQU?5|*=z1sqy`&J?w0;&sQS}ri-kv37vGzEZ%TKwx%>zdhtFm=$e$hUk z;paW>SV=4&F6PeG4t2T+E0g@xKP4QR0vF?WZ=BPkG@S;7oB!E(_h~9eD_|Kd{`w}0 zV(!ZZDYMU9;yM~l18Zw}S^A&suSGwS(uugWHvL_>n4cO}c3P$Ie(f+l7CB!<-Dq46 zH4fq^ioK=GN*BBUz*N+v+P%Crg)YjA-DG?l(!W@@B+d3o4%v?_hD8yCEsM(oWGGC6 zi^62;z{j3XkwMtrxI6C1o}{#CjsnGqj;dpUI^nNdeF7rk1Nm7njEbFM4bPGK$4GcS zB1AjCDW<7%N$2wUQMen<OJ|B!$=Z8?l+AYXDd*+>&*MjC-8;|IX{i{X2ax48^|7}E z(5`kwm@T@SY^~Bo`0^vV)Tbeqk=>@d$R!?rRF!Z1-d+)1?R3@zRO#q5#r%|yZ~Dag z1K_uyCJ>SA*Ab#1*uAsQkTspZht|_i#knkwV&<YGUS`Rj-5!K$pYQkF)q?#!HQB}m z_h&q=St*{p_0V2|m8zja-g6AT)V)2fQgC?h-q2fff-?XOMvm3Wtar!WX;Dfg*r>{O zSyKooxjMxdO6L+g4A*5w=ynJ&PJa$E%t7&jAG%$}7{@x91REL^l@9-OSreqQ9270t zb2=a{m=u{#8M1L*M|t(1LJ+eV2@XZ7aO8J%^=T1UH{9_`3U#&9C<UCC86T1~EUHD; z`D3MTjuS<qtDI2Py=#jj0w(ry<JF0lK+aU+dJ&&g>|Qz}U^NOs1XRuSOa1aMb{u4; z8;9Oor>?S}<YjfSi}k(0kusip15;OMQA(>+ku%o5g}lpFzv9@!xjBk3@=WiN%J=hq z<*8#&wY8>ib?2r1__;B3?iY$cDWxHxJ)d)6H68cu1b32KRGxu<njDp`_&)4(<E-CH zV`W?IUF@t$vH$7e8ME{XDLHy&pqlvl>;))S?niPV6-bqyoy7m+eYnm!xPsh&QR=(b zdbHqFmC`Nuf>lnQ$7q^(3RH>Lhi(;zyv$SU?b#muQ@1+}+$s_E`Y~mPzdHS{mR`FJ z@-VgCEdX7~K{N{MEL1*$-+1j>y0qm3tR*=>-1F6|s4SmbZ7Gp+1LzgwcHUa|3ly__ zxvXD`S}vKjni$ESf(w1G#E<~;dn{W0s|Rm_41hSI83ypeR7J^K?M_r2O0vp^e(7AX zELu>HsPJYqUOIjqI{E6pjXW1>oGKUJjcTObk;j6`1%IzpH(tp!NdUw?W`J`PIN~J6 z!WpTOewI<d<lw>_7JOYHNu%-j1pM@~`s1B*DtXss2&N`<5ID7W_23_NEWcc<s~+F_ zE_r*7F}hM5y_?j_WPD!d&{?ywdI%6V4Wa=KLJx1Syl#XvZpUx9@XKrS_oQr7o~eOP z*_Oxi!tzxBCR71HheC`ww06bLg0gOMb=YANSwV3cK>sU=wpmZtVy;O{l6qE<olbN7 zSERsXd$_~FTG#w1P?%D(TgxIT$Ny$kA;gP0UzmPre*yxvo1nrYsIO^L5dar!y28qj zyX<wCLI|*tQ^zHe5Jf+@W`_NTj5Wv=4`)-)kvWtB3{T%Z`<;YC*|Zh_m0XjtGW5cm z<KY}+LU+ybfRS`06^6}yESxs95(mU)d_BsvT<JIA5RH`+sbEGWfRJX^{a!5tY<_5K zQ#kYrAOPld^(`|dgVJ7YIw4O4Euf;5JEC}?4~fp2$<BT1EBC`KiEn^b9^ar2tW@o~ z`?pRoY>SAprijuoaLAJ#NNt1lfkM@9>Mx8MChsUtZ_0R=w8JWHZ#w*3c>!7jDzoz+ z`AsQy<K12_8&G!KGwC4(&1-7%NMm^ne7VZvxwesCd~XDf{-4vR_E*>G;s2k`s{*6> zZ!E&=;r+ML-Srpw*E9Xa1)Qf;R`~e<H2(!qgtTz(qZCD!%E`1T`)+1X)^zMo9TVav z)K;-NyB0_MlRNm1{D6c8S`(ZjWpnN!<a1lwWw;A9cgM40<^n)zpU!Coz<T!xb9}p@ zZ)k#6gnvFE#CN3q-`TtJ<<<+@l|jjz^H~><aT|fOOZ@q*kAwqV-x2uS9P#3GU4^gy zEo0Vs?1oN)sG@9aYN~d=3n?g&v!Y^OPfx_g%jo;Pd(C%L@o%UOOxhqQD<^w<l}fem zfCBM#bZm2ys;aocWtiVQr=fmzl1C0w_3z%d<xf>&$q5PJ1d{)|&k*zy5{CVCHMf@Y z0zo~(|7d`h5wTLs5rnaDcmhB$mHBQQv%M`EJ8ZSU?(`qx;%?tZ0c!ycDym38VY}O9 z07MqBw*O5_^sSJ@wwdzLDwUN@AKV3nwf8I8+TZFQ9lr4&ZBCf9Q!9?=pfGj?sdDWS zX*4Y-<^z?4u(@DR59`TZza4*Fts~S2AR$J|+6$$=1I!U1+6Fpzd7p7&28F}r+Z&;P z9go@McG=@8aaw^^9>PdLD+%q{svKU~%%HeH^}=VTcx;{5cAx3oOJ%_WxSxk=Y$a>N z-PbPzs-ivY@&G5(84wp5lmb-+t&ju$2Dtv`j!*-g{eP%r9Br)L$K`MLf}SUR={Cg* zg5|3~KQ(X1s~3<<=RDk|UHA#R4-}zuKLc^yEA}q_Z_8Y^r%1M7+4Mv+3JykjYU7n< zets|)>O3uto#z*k2h{?G!$1&#aL(Uc``*hRb~&|4j1|NHqgWjF6uRhBX%3#+x*P0v z!}$}BG<E+|9+`E4>}>cDZbt}k$${!XJv%kNjLK4@L@9lyO1hMs*>1;x%lWtSLd6p< zowy7pV6RZRJ~ZtZ%VQZzWQ{_XtfHta1UWEaJiqjH01l<YePEpfQyGKS+Z7i!cXwuf z$(Oye_)s?zX9rBh;hJ6?_^`LHTM0pnX|KOx$sMU5`E&1w^#kCD!7sobL2(f+&j!}2 zeYMZ2NZ8buQJ)6$^@V{`@Nu`VAB_oBp|U{+oOl}5+0@;*Lj%n9KR-O|64EzLxjLOw z0TAV7lSbhDXW-)XNLBH=lckHBPCCyMp4QR4PX?YKXSjaVB0spqO-F0;xKqR7>ooOV zspmd`di~Rrz{Q@f6JFkn#*FEtzDNC=QPKZ}1psph3%wSHQ!b{|Ba=gHx#O?+GvG@V zfFt}0x14loeYQ41{(H4{!fyaQBmT~{n7zm=Je8$**qi?AP0*{~?EEhd06H#JxJ(jP zYu5PLsrTKi{WQgD$)KavUz_by0ih?iN*I*I;-&8qQ$gJ*GW4BX_6gNybU?XmA|gsT z$R3jXY%am$TY>7Y39hG6G3(Uh99t9Uf1>jHxZFxhOB|^zaJ%Zh2sLk>!$T=-4P}B< zqsH{TUO^{XVD6FsMxP83x^Q@DuY^c14zGJtc7Kc7#xGL$2_^l*H?1=82WqBww}B&t zukJXKaRE}Rlgcg$xEjw1RD-zHTpH2<xM6dt>~-(p?Ph|7B1$AD`SYT$P)D`U3o0O< zs6}2&a@skRrggA%==Y87r;>(%Jiym|1J{2y3r7WAF4JMg--o~6DN@~`K`{meaCpj} z=?0Jt=j2iAMz2BfK#mI6$I)t;C1<%L(Z2oPOMlBKLFnLH-@es7%dlewmcH<xRw|s+ zaF5C3Ixn&_0-P*GV%WBil}kjGL4=_BgzMeQQNf^xGG1K%yFast)A#&$13-u|ul;}a zH9#$Z>;J+<yuMC8ae$fXhKV^5q<>SPTGNohr7KZTjRZr%zb(s`9UcPk3_ak8K;((? z8Nxj7^LAuU??3yd{6Z#2zCGRiioir5{E)Fd-N+##Ok)ygM)yxC)j5MFx#HS;(_+z~ zady{wM9Ue*-fTFnr6N9@S3BApsYV7Y#lHdts6AIJ+|JC2^>8e#(C-hoIjM9fV)^=2 zYmq<`9uF!LfzXOJtEzv4VHuzUtihnn#)Riksr-_FGT1E^m<6BO4d4)ME&1J_qqk%A z;s~5yxY}S?fa4^l*QWwh@h-QejkkIOA?$TD6f;1m$0m6V4l~bZKioFYy3HlrtYJF? zESswDwa1ZXX?~NWxG2>?IbrvEHoQ1H6<AN^>;H$y`lu<dHmUG&<0|!@-S2O9LAQ4+ z9Du*UU?aW60nGP5RW=Jdj1?|_5Pd2Bktob}*z`GYZX#xBP6g1vuQdaw!hCJs##So` zXHJXQN$;g%5XnL=1t2Vzf12^0AKx}^(KiJqSaIO5S;IpJejC;8y#b-%T__?2TCEP< zqy^m4+Uiqj&f7Y$uD;*eRuGw+3E}ZXiFLw%LV#VcSxWj^qXXV11mH^qM;8O0b7a3& zhJDP&_MtXgNq~ZH4p+DvWq7;3iV$^hi=E%5x+6h`Ct%a;*9xS1x5`GXeF`lvV#v#X zul2)2mT;J-EsqJFHb+UQer~x?*`YHTJvv|m&kP7nZd?mnovE!Tao`m2NaokkFmU}} zsNVm`e}n*s8&sS6u2yv7ZJEz7`^nU57UGx7;fKV<qvb$E06L}uv?g%s)jlp3(zv&< zz@&z|6B-)&zNty_l7f@)Sm)lIGFo~ggwJ>sG2IwN?P6>27f4gO*-oR!dUJ|k5m8oE zcdU}yFsuEHR*|On-=+ni!)l-qkyUaw=fi-w*p`#c8J2v;xRCAp*g1(s@=&!s%pb3g z=MQTG?vUIE(o3myr@5AblKVud$!-Qa1p}1z)ubGVFpwX+cA{VuVCx)eVXcbTrh`ga z?i}C_Da~b}8HV_H=k_zRvp~@SG;jPCXkEd{$!R;CqcY`FtW9h)TI7}63}Rq_9v}bt z@+bW4kU`W<^oqF&m;-kuclO!gHly+NqljxVV}<SbwjT{m^5N`3aX=|OZ5kw{AMDPh zS1+A2JP`sc-uOaJ=ai4iqZv|KXE&b8$@7zaJzJE+M73EBgdkIP;o$6W!|AHPL`7n6 z^M5h-o>5J0{kJfR!ci&5f{3)ENN)ntn~H#ffPjE>kt!uXkQyK=0xC^<@6t=?9ik#N z6ltM`P!d|GfdoPb+@16MpZ9*dW4vR$?-(~<U<^pI_TFp#%3O2K<v7++t#kd>8g%>M zocZChUWXi|Y{wNP?ebF9#{-w!sR_>_Ek|2I>@?p)yN(GPfv0_~cYaPhk+6W*janim zYl8zZ)sFj&n`38IkVd5&`1ezg;p~`f5^63aJo<@xwwx#aokxACxAUaa3-}Q2AtU_s zg|&OXrghs<|CuYQql0DW$&QbgmsiHa?SwgL^Tx6=m${Ol`FX1bD}Ntb<e}=;OuI?t z@mlq=ipspV^F*;>>&pAq0N?E?G4$7$m5H0EnzpkI?fA>H_7K}6oGRVr(510q_<Zfo z!h8_Dq2ZXMSSE7)UEbgDvuZx@@CRCY=ANu5?WawCcU1KA^%1hahGCBnD><_>4^oPX zG(SjXsa)TuZ2E^YamV-UAAK<d!+({AKX^M{71U}Rva`J^S8yUQWWI~*)-lH>Xy!*3 zegEEn*r@U9X<5=>7(<8l5LRw4tDSyD^cDE%X@Zgk>(X`1hq#@W%NmT_TvGiATw37; zcevt2^|>}d+{fjqg*2VzrJ>oDY`=rt7}da8B&x}O6}WjbE!=JHN3zzsmK{ra%^LpE z>j$Y*-xf+aXX@jwBV_lEu~V)yZKyrBIPDyDVB_-<5C{cYhce+SjSzKJG_L6FWJ5vV zm!;-FdHZqNn0{AVoQ`hCJ)Zt0Ym<|DkgbHd)-c6?B*jzjHUpjcPD$h~(~t8VQeeUk zyjMh2s}Ek4bZ`)9g7&j6?AhFOCI8^j%*Ws+?S=WFM_oZY;T>e6wz{>cGV#MzU@gW* z*4#aOJw(P%2BtcWETK11o!e}$_25z+r>E_L*$R9DY-d9mUnpLK$HPU#n<Eut35QnV zu6l|6>HZ%(WAjJyxdWer>?De~6;Df!j-K?~0gKzaCUK{1Dc;4JhD@V%<DX4=p7)n| z#5FqY#hUV*kNnV|PIG&uTW2;nMG7rJBmS!CVDK!+@8DJ4@-<8!6UmG?3p5hR&#v!} zwnp|0w;vS-8gdlPaW?C91gA9~b;cAS1hf5}AAbl}cL>%8-;A(d`ko9=kwB<C0Tl&q z&DUCIeFFRX0{W@hn=y~hzpp8?Ld`_CD<9ACgv37wEja)V1$yvkyM+^Cc(%J7dAeyI zxQOvA?JY5@=K-e$jZXwgzR0`Rqs#pi3$1~`;)y);(wsYdI&vk}2u-iSTzjaSzE`n4 zH8s^lfVwvv&O`0{cO1@6rhc>HCzv@atXgx2Ef1|)-ZlTI*1`^cYVnLwi5I8zr|^T4 z2D<UJm?QlJP1l8QWNf8;A^KOjMLls|e;HqZKHn!>jQ$Zz8inQm{vA>Cm$<FR2iu8_ zbomkZ>k)&V0~gk2M|gYU1wcEh4&<Kx%H1%6g0HJ1v%aG5R;+mH+~bdB$iJ>^jaOHQ z{FHV_eowKlgd^R-4A+1)D6S$~9o#+x1gW@*r#%hG(CV%(dJXh!qh8FFy-S9wsh;?8 zwpjtwmIVJoP^Q@uH<{n1xz;ykrtD_lI`?g8ee5pA*V<*iyt=|1mzM}-6|(|NWA-<% z_x{@4A662J@j2KtG*mf0`4xI$c2^oop$s;9aRqpM7nu9B$2`UEzJv1hnA~p1b5;|E zteeJFOhb;nXB%C373~BeqqQ)3vBka~?(OkvRM+?K?(nm>xCYCH4>f7WYrk28(<lW3 zypryS;b-s;-`-jw{Lu|RuHX7&z#evsFKFhw4OZ(z;(6L}=ZWV1J#hD4jIyxs1o&gE z<8YrX0f`fzDuhxh1o543vn|UeqM=UjYCrP@2eV)-G^3yN{c&OEb!>xfUXR9ob((Sd z-5!ENmZW9`nsQ}JL*1%k0v54noDmA3oy46H;8N&}nC2RS4(Sbxrro2&BKq35*UZc+ zXfe<$U~)F(c;x0_`Zmgu0_!x!ur?P!NY+uCDTm9G@?jZ`=v3#kVZ%z)e2!VEVHp7K zBq;y8`t9D3z%%r|jJLN^&e?_P@LeXwdH*u8g2HS<fM{~o&52S(d%X1uKJKda9qbR) zN&ke1R%vf(biL}SnGJa&(OX9xNobQSkJJbWQ+dtpy@Xjexb)-9PV~{EM2xVt7iiZ1 zLeCxRU`mKBOzDPX$aD)CJzWd(Gl$e1Z=`d7W%?nhN8Cc$cJ)s*%$uSI>*-&UX~Ey= zw-K#O{=^JYNy%H{BDZevehGi~bc^P^BCxSKj}QD*;d?3$4?f*%4W6urz?=F0aB*#y zK>?6&q$?x1Ycw0rSYn#r63BS_kMBm2e|2EbP}^@8nRHdsi3>TQX1m3o;@+T_oEZdj z9Hyw>+~4Zl;Z*P-HjZ)lZeUrn+5+X(ebxv7=j)=NT+>E3JZZL4xuZ5lx-TYpSASN( ze-uNWR@I{{Yz5Ai8<hOkNhNnie()t+ZEkea*41s;$yAE@2RDy@-QYI%n5cV(-&pbY z_qT%X$<XAK=Tb>0Z|j}Iwos(x6MJ-vQSEttIlncho#4(S5eg+NVg|ZU)!yo|N{l9b zH&15jD$i1;U@ky>aY>V|qp2yic?okH^&Cop<MXI|bAHHiVre)XGus?`9)fW?V4OWr zIqnT1ZgWG;oU{WZi+85j-m(iIzT}57H+XYLDA;7ba*#LWt3PaMYKnxcsr2w7>aF1| z=~sRX>jsn`t(#!x^Id-aD(KXVai6Yqe1*EX&GOi&G7*Ew3N9#K=HFMy)030ogKoys z?-y9drZc;7YR0Hf)At)H85Xq#QMgD)uk>tsKejFZ<R<@k+N0>+hY`{9nS#V{DT-C* z8=wZNORZtiqLdkoBB;pg$^sBBshCH<LTUt1KmA!D2)~KmN!{zB?+jx|D3L=yxSkzs zYP|UM)#snDJs5sWvg)(TI{yRuJv)n#0ApI&|EM<0k<b<qBC33RR&|F+n293#0$~GL z?TfJRrGymXW3h({_4_le{c}gQpaM#ff5g`HIpv%fii25nSD@*OdbE+8yKQCrhT_K2 zC4DVd7Q~x4whqfx#T`y*UE^F|&22?5zJ?zW1y~RBhsuHX`aS(3R&*j>H7);^bf&xT zQp8oO$CkHz2O-UE#+J^GgyHUcmT;CS)Vs(L=ktc;X%<ugFN^oH8i_)QKt^}oW|oTP zngToRbki}xPk~&Hh_k)A-|x5i>UnF&+hQ&M<?g)G$@y!h`OUnOBfzu9wAvjX3m5OB z>^H(>BjU9EoR=o(nFfzGASB=UkG#Y?%yM(LXR2rXgRoL)vcW>>zR{Hsl=^?MfbZ9@ z$3KyH=_yy_p%$Y|`q<9f(CY?K3Y<c@w;g8$dgDePSu}={WLnq4dZKM3D*vn~s;F~| zMF=dsVv8yaG*AELp+EQZA~$!BRgYujIT{dEcP!94Iywr2v(-e2EUJ)xcSftd8O{<8 ze1<e`s&|?2NRbmmUnrP*-yFHWzt4E7`B-VO$%_@Q^EW$$u+Y0<9aZ(hlZPh^<>pc^ z49+fi0928ijdDr$^Oc#ivqi2i&Yb<_`DDbbe-9ptJt(&xMlK!|&khW@fJ|<(SUwy> z<KEVb<xLrvw*^F$lQkI?y<I0`&^k~;!81IsOT(mIHjEND(lxb9N7zJZMr+^*$G-|) zr*bN7ccz<8vORh_?XAmge;Q{a=GlWz>}<{+oukRPK%Iq^C<xIAHg<yQ%T|z?u8(Cw zdE;c|nM(ZgGgfY4?0&_{56-u~-6wjp%Y4EA>ghpLo4|(qWIxzyFJpyI-zl}3nwmxg zTiWa-j#SD`&b2f*>)i5%w;xWk&e!>(RRdCNW2dY4nk`@!t;^uxEcjHlY@3bM`Est? zuG~#F%f^tybtwtBh?$tyM6(yG>tRmK+iCm;OO?F5e3>z<HTY(;Kk-C7=wv#Xo|$t{ zHcBbo7U#6^w~IYTrd-K;B)Q>BXKA>c%+<4CqEgRWpRIYwQLh99)-DL~s27Tkm*=W1 z$sYK-pnn#(!M}AJ!v+{;P+O8zpVPpTGWRxCgEG^zR?T*x*q#se-EeX5P<2|PeUux; z0h1&g!ftU`)UT;W-S#A?3`_dG+m_hP*K?rO5kSdCY{psXN58yOx|t&H7)U??H7$or z#r1CM_KG^fGipNb;4v^Z29{HN&$I+{40J76Uun5}-qAhQ1^&jEDVF7BPoD-OyhlPf z8oHTk53kVD(NXloAB+vRJHXTH4*rDj*jF}qhbttMW2e798}2xsJl5fc5{<$uDG|jv z^BG!+=Q$#ZCF6k#`|DgdElPG}uXycaS*xCk%Fw*%z8AtrIotn~P%-Pn%Wr+`J+0O3 zB04i~!>$;(h61w?zraa8x@E-nm6N*rf`X<FX3d7mWTseJ_3aVJz#^S3UMFxNm*4KK zva+(;%B~F$tl|AP_LBvQ|JDF!Oya?BLvc|v{47u{M^dxI8*Tl1=SRftUS%>As5gDe zf$sU=vT*i2gS_Nz`NI4_71Hh&yO`aYGOZos*040rt({ezPx)|Z;&FjU{WP~vK<k*( z1K)Q$t}vs1%Z437nYd2C^2F-#!G_Ap>DAhfY!~(>ugR>2%wr39Mn^j&vJHH=!d_}7 z?KJi6oEOvz19)7b6@^Tg-f<WdXklYi{mkj)&D{e7mwrIDTZ7-?-*p2;b>kruyQIfD zUUjoQJ9sZDaJt2wC_E&U5us<?23Q8}gmbUX2FQYDR?OF(f3{T(_aVfUn!yvd{keW3 zg;lF1`}>oF&O?N^e>^<faSWcv4doK`B^BmnS0s802yOXg70VTq*~#i)2n0Xvb+dPp z-~uH_v2OW>xy;<N=&&<uAZT!_%G({iGH+B8%`-03u4Z{RIm9`Cf(~y0#rGX7s9op2 zaow%`xULp#3AEqcRB}oU0x>qWw={gd@B6`EvZ4LMYhAnhG1h&Xz7!9(eGU10?SaE1 z?VR!|X~ho>RsG7==ui6hFQS9?O9Z}LTE@c;Isn5>nS4#A)RS!&()QVAvA!RrYai|v zhzn=vaM`5gsm!j?Y}9q{Ahrp(0g271R)c^GM=%e^$EaE?h)BwapO9uDbOhF}e`LwC z?kd3I@aq(`w6I7LOG8zwrT^mlc4t44H9KHwq?&lA2BfsSFNlAl)**;Jm#uo3eix(= zbbY4L#ppm31~@-a1uFfOp9`bJvJ_jkhk5b*!K=|*n~a(vABBMUY;&S^I7qS482?$p ztT1QS<OGz}lDdI%B%M?{S4K~uY9tUMV}er%q02geebn=Xn&&s&p=56mOtGc^SB`O6 zd+>Q?=0owKpeCEa(JbYb2d*znxXjrK?CzBTxmc+IJfH0VU0+=l*2?oPbBOkQDz-nQ zq6`qpQj7RFX2^%THoge60ufGx=mC)!mp)VI?PYp|s`n-khDRvQ$C|=Pr}u{6P32iS zJ>AtboIehl9wu>(8VX??)j*wwdXe#(-8aWwK85yA^fBvwpdZr+7Fiu6nStVz-V@b? zz^dIbdbh}fktNaB(X!yR*r0SOl7hH-6};F`w}Uf(gd9No;|k7EAhSgY`X?}>2&^d= zTL^BvQV@Nk`Bigechqf$wzA>*OlY4W4GDx5MTgS1%J$~fWvJ(m^j3$^%?lPKK-J%x zcO3ORyWS%k;bf7a#7uKE44~ZFq?tRwZ>A!@?5@(a&rePo=G=-_=X4)0uN`r!BlJJH z!_)Y%Pg!O!l+7CIj%$!^y!m5`?_Y?!^u|()<EL{!++r@@r;&@5R~6-uU0k=mDXMt= zx(oNwVRwaESWJBp`YQ{C>z4BK91OB@Rt^H1XDO{QINnVe0Wg{b-nlFw_dJch<GAAM zg)RI1wj-M~O_<cn`zs>pgg7Cqnjz=E0w7!DA7l-tOP5Ol5dWbm6{*COm=_^|wK&o0 zDElGqv!bsRGMF>`-R7Saimw%bIOaqGq@(xOoAb?AOSs$G-OqlulbN<vJzinv=bm-| z7)9@Z%+YqR7H@L*oRXei3>^VCr+PdO^qI?7<$!@bn!9l2q5N;1Dw>5tPXJ!W-dZjC zS8Diwk^L_K@%?X*{spkDlQFdn3v966(VzC@6FbdyRDC|&d#&+@l>6XR+kTFKct=+4 z$*ey-LUO0po%O6~SMMC4kfah4h)F34_)#(%!+!MVv!c07iTQ7f?7-ST=wZj-C{*ZJ z+0|>9^=+XSw@+t>C;C$McY;(7#~Qi3ye6i@E?hkJ4`{js?1uvY>qGzhP3}d`M&~!% zjfBZpZVbmWGu{*^!a!Mpr%g|-oS5y7E3Ad@p??&V$#BaB7$kk11kJ?9CK|zw*LFXk z0^5{2NN>Axi^sqp3Fe3WaCem;=!=4{5N6IZHv)41OCJ}g#aIW&xHj>q<YjL)v+ChU zyRSE@dB^Fw#p6ceRa%H|mFV1?tHbd9M2^u~uPd|P_Afny1?{5B^rD$6uYHS4+Z%(q z<mqNerw-P4md-2Yq+4GUmmkD&2Z^)J7r6+}iby$#Pqa93aUhF6vrPZCY&t@?<Mev0 z?x%|~;yy)ljCUGi$*;SWQi)QMwagsKSnN8oRl5v%m5>aZuMx#p_1PuO^0D&rBao{i zXI_ZdWeL`6aTH1$#J678!5j}fBpK&jPW2lD5h*5>?OOI{o0}Q5n!5d+6E$z!fBO0u z2v#Js^72_`weJqcI}Z8#w;XOETdG14?c1mV?Bbe~=c@Sev0r~^qszz(rWobqqsf)t z{2p6_G09T8i*t%OE>06#4W_{q+p$v~{$a}|ff%>tV`(nP>l}_IubmNZb7IC9?cP3# z-kR0l)gi-K+DFdY?%C~>rn#)6h-9l&C2cX%>791W?}*?tbI??63`0DMy>Z97*3<V_ z7or9{TcTq-yV!EN^^Ki47AH++%6?JyQm4~}rAho^aOGw54xWfE{A!q`xi9xuy|$O~ ze@wXM6%|ZXU-A!SknKswQ~O`GD`)r^o%<i)mhuEQtN5+8;<489K1(iuH1g_VJr`Th zq$66aashh>jV_L_<{n5n`yJvdenQ##9g$pik;J@?R=;_MP4Hrc#kwa^lzL}$V!<J3 z$ljf4QB_xH`D};%WbSwEl5?jl+ndN1^;kw>pah|ADfZXau3mQuGxm2l`8FUz@!dBO zGut~%fsmCRw7F|&B-t8HsP*VV;S0=c_w91xY~JqeK!>e~$<Heqsu9NSY${c)9QKl_ zo7%<#Pr{8C*&DWM=@;OQTd<oq{uzS}baMyQKP*fu>S2y05fR+UeZTQT`?f}<>~ns3 zKUkg#zGX8k9L9|{^i`c&CTd|z35qHw;Ge+UG!Q7i8f1>SDv#)GZAH|HA9@b|bYk|M zd=#Xh!Sy+<@c>3wuO@u)=(Y}Dlm_L;A1jtXr9*BNdFZ-=Y-WQ#<#yh!uUWo`$`G|< zZ?;kR{+F@Uj#P5`^JPiL{)GnwoVA7bjBHkib++xd&e{X{P*H2HPW_PWViVyfVp<(n zROY$B>9sYrYNd3<^%=A6JcfPlpyq|GRjvenaXU=>Ba^WC1)&d;-?+c1@Fagr%#_++ zWyz;M4BY&XhPQYh*#oYekuGIp=r)vC#nL~dd)q9b&p$ka6i{?6(zbI=60|JrVG`~1 z-By-y+;grsn<(*K#-W)~uVDqRHCy@LUllPEP5#pq{HlYq`P9qk0KaAT54vqRhk>+6 z_%+0Tk9$aGq%YY}HA4datTQcUUUk7DaFi7-8rw<)LRN&_rnrjAUF+*>fk^jRU-W_^ zh77D)_KvLmS+clt2KkcE_);Pi5f!U%%GGJh|0ej<yJGINkkRE5V2vC>RZsfw?oGm# z4mQJy$qdX5i;_f!*0QpJdy+c!?rdV(&KHNov<od~)q5`~DbHctDZpc)v<Jb1TxT5^ z%^p-P=jkH~<cB7jb>buXlJ&YXSew<AOn=rIi8HHb#!b9Vt+N-2E#s0Zj}gh)Qu*7v zvt7Vdy+(jv+lMxwK6za@CvUj-k#9zfUQLBbTrBygO}qWu?GL#@b_a=Xw>lsJGVT_R zo4bnMW~{Sfkf!GTX?vAqPPcT~ZzDm5)$Y%X4%!%9Om?P9V>x(3@))W|ipuZoFO0;q z#v5Xc`%uaBHfxra_BIWF+ClSGruj3V%9(A>w7#`g8=Q)KmCq|_e?R-5&3UlHOw??z z!A50J4`x?e@a;CJB`wzon|hIyMcf!QU;^?<LaceEC7o)shN*qU!j|BnB^R6d<7kDe z!g}2IEVY_5(z0ytXGJSp!;Gsq#I!RBF5w58<Bx{~5VeE9Two-~A;CRdY2+U@zca-d zWQqRZ?3ktTrWZrl5=Tp3vO5oM3Y??#tLB?9`mk!D9}LCr!ew@13G?(}2XWcYn&&^X zD{f9f9!M+Ke(nrb3nBY3T)7lSU!*r_jEBc2)E(UAlerZI?hHdsROq+gCvEp$NVMks zFBUL2TkX9}FehFH9PuK7q9uWzRC%reebh7AGp6zfF=okEaNaRSukBt)UgXbcT6<)i z{v)54%z(MM1z2>E;OsirVI7vFOdo?Igb_u5VLeP!6Z-DenOVhKvd!CljnI33Q`TVW zE9QJm=I6wp^PaDNb!j*7l^Y2e)iK6SD6yS=tUM4(yxdGKdCS&d)to6gk=^Q{&-XBv zJzc$;MZ*1J56|Wz-}S=HZ$|H?-Qd^ElguNEgAp9KsrH@SOVf97-NHy$6lCqmNtYcP z2>Y#SE@Z6fk&bD5Af<EX7tR>QvBn=?%Mb;IVMk8UMXDMP%2^hybKi!TNy<me-4Km2 zhjT6U?xkEhM0p=PDIe&x;@Z{<t<i8woU`?7c0Vi|thGPqGo0&W`lE3|eal_*jloxM zS5#XZtZb;jVS7{d<pJIelP)afQL$lEnQgLSWuER>@+{<b4UMp>x>a-b)_G<!Bk9<0 zTEQ-owORCvoxbIbjy-UvN{Ym=XU^6Sz%`P}q!VDi+(!56mDHlNj*@87;}w(7xs=x4 z%XS}l>=~=RO+P6@M!{3bv+cAkZZrNlZ~a6oWW+0{D!Ds?rSQ^n_3Ewcl~0FI-#^p~ zx_+=t)GFu<{wq-pmNC}waZr(2jN-N56iX30K7P4NeAVLYh56~EucSVle)bn@ozbM8 z@rBuID?qSWo=#Z|5;ERR;zbLNt+GWS9Tp{<+erhotwhc!xF2a!RYsKTwU4;2+4o^s zMbfn=i^r2y3^eWU0VD)H4ymU|bsUo6)Wz%j^At&4!~@~|?*pCqI`vmi?aBIJB@4*{ z#lo>b_wZ$|l7ud|N5;SLoPr4#8N5$!ummYxAzQ3kYj=2?yVAng9Awh>`GzRu<L~6A z0uGu4eyYu|<M)wbQKq+Y0)KWEMhx>Xc1ACZ|Cw}0L3Y>rfH>+#3#5X=LdX8|H5Ia= z%2Ff)=Nve!SM;``ijxUl!%{h~K-JQop{IJ?-HtnKCmWxP%8c4epI6n$0+nIX5c{j@ z$697B?d_E!oU=<VABHlbd^eeP_hUJG^m_U>Ku;4NrUo-=ER&Azkss8Q2Wb}F%%>yx zt%g=78%kjk?#h-o08w%HP21i9W#Z#`B1Yq2#>NQSdP26Ibq>4G_m9xN$9UgV@%8E& zNmuii57o1!m|DFgSK8+AxW+%mM5Q3&#|o3I206ptN4<y5LPL0Lkvb@ya%|V*Z@YA@ zatP8rvxFiF3PozT>1;Y^ODubExgb6doL7ne0PcL*Ptnx947lO}oxpqiKQdyA9tUh> z;dKM&kugL9FLZ6Tw%<~Dmh-DZyGK{p@9zlkLauF5a`~LV*GI;~cH42hn()i>gvx{* z_Hm}fF9FD5IAVdH^$2y62&6#($Y!c!Gqa!Uuw_e~zS~&i3^#6eY8X8@nAWyeNl5o4 z1}>f0<0NYj@}@qPUC8*VEbbNEXJ^$B5V7UZ)d-rW^DaZsO5#>oz+(P)Ula3GBLq@G zs351qQ-Nkh7n$v(D?6hNB^&~3o3X`Ge6^-oDn|iB?xx3WnW*Z+wXh0ujhB0gx7<dZ z(rrnDe%hMNVbKOzOD+%6<=(+mg^CD^p=f1I+<2YUo-p%i7v`lugBvSN#2js_GX8TQ zk7R94qdHd-uz7Lw&*g_=Mcz+>NU`69`z3Bc&1(3BTNgsIcf5=;um~O=`qLJ0eEDlc zSO2$@*|Z99lM++1SW%y>8P|t3Lr;9?bj>qM*j?@o^N~q&G0_#*j{=wcoz=Lm&+=lc zSFas%D%hZ_<}J<o#^|B$XmPZr|EYS1Ay5!eHv!oIQ|BC|Z+kzgeP8b|5j7cEiVf5G z6c0Irj+$ehY%e?8J=-k_-!VO1y=jUt5;RIY5gr$qSI=sWa2Bz(nO$MrRJ(DxE1ns} zV>?POwK6NV6X<aAqF#$vvv1UXv><^MP1flCQn|%*dqNl)lw!|vP{6MtW>I~*@6L*d z1WL}~*)dAkVS>+iMbHyGwoX>{_>oVzaoKpS7ot5p8r|Y=?-Q_*3vm#)ARN=9`#xgc zTWyK2SZSE&1S`*onYUx9#T#&D#WyR0i}^55WK%17uFJ(L7+R>(^QYQ~M{T>l=dNtT z>2XiDe1r)CR1vX~6`WaSOw@a>CI4<SVe1;mUn|dO?AUS^tL*l)qgE0%G06C%MvLR7 z0qMDG8)eXK<a;GU9TYSr?>OQNRn7hxq+kV&aA2P$1|DGp>&<x4^+1VyQ_i$w*hnH7 z>bnySs#Hi_gl9I6x=Q#CWOl<oPS#!z8?-*2ys{H4s!F4C8yQy{akqKHBH*3#>7Fe7 zvvAC#Yh5pjG^G8{ua$8pBgQ1~3y23DZ+p(o7N7do?H_oUH~J3`Ya;{vTa0%1Hs=bz z0F<yH;Gh>?xez9r#*3y<Nz+e{V21?4ji#<V-R|Hc?8igvfT_JOa{<0o7VcK)LkaHQ z3nz+k@na9nDzxr=4O&+K+-d>RCk-fT+BG(`RfF*BDd1_Hvq>d?fkuxjAkb59x6T%u zd^*$}poi^ON_$8v5KeW+myMzl$t9?SEZ|iH5ICqm|8G8m2N>S}?!o`xQ`G*CJRwk) zy!>=ViqkWl`ZdC1SYyib^E6uNj|;AuRXpx!YkPTmdfD0<AlwO3Q>uJK*E?eQ9_Cr- z<J^Ni{bTYKR@P7ro&>FPGzCMh4YwHX@@sxCU}NH_>gD#3b}lY`dG&HA0Car1D`hB7 zlM`6|>QmochJbb$tVqD}W`w-Tyx&RFuQ&Qe6+&%o7idme2TDqkY8^!{hqf4t+P}^b zaO>-Pd%ME8;2&$?;Q^2P;icXK7rA#&O-tfR4S3JdoLwwXA{fIvNGJZCKSOSZK4$y- zAAqh8m>C0XNMvvfm(gT-$?opoG`rab#+Bu}2M;(^L<Ssqi!d}feoYpP0Ao2Wi4W>j z>OB4r<-7n7W-?Y%WZ)g<Gq1GK|H!DiURU2@;&}10tkA>jI?W3QfjLy-&!62P^cAwI zz%-_EQ9%R1T^a3CH_iP>^IlS8aXJ4*8Fn(^zkmJh^dM6%x7fmz=3(kRcn)SV;iTrj zhL>~$mo6cDPBz{FM9_Po>FJO2tAkC?(R`&clGyL_^9u(B0t#=N%i`i*T3-P^AC++` z`?{~+psD3DjX>uOF0Q15cL2lh8g&>yG7@<$02N1q{p~1Teof#4zTc#IpVx;@xE)3F zzUDdI>vpQl;or}eNM{jB1V;Nj*Vu~{@Nl5&s^=sfYUL06GWr3Fi#TmvVA`G9p8P}m ze$;WUMVivZYN#2d1>Ke>X>lqmPzsVGTYbKqy#%uAx}K^VZYV`b9+&Q3ncWRO4cw@* zS&3lng7<bv6dD#`W<*O$el=sutujxKmX{m~RE}~y5jW5I3vl_pS35`J@dTheXc+|r zAd{_c`)rNYxx?CVV&140aLV4Ae&<kTDtB-(2sN{n8~&*kGJ@zmKJoH0h$FMRe>T^! ztVsKX&`w~qceVwwu6+7*z@Js+Ev%%JFj3}0&-8K<CVr~Ow$om8`L91wY&`u8Mq4u< zV{X#?pz`jt47*vaQ}i;y5G;##XBhEA8nOb57%wqgse6mZ66d^@;Dwwj771+B4;OE_ z%9D_ryVBH@QSTzfuSYyNvJHj7ez0tdcL)G0l*$A%;8FYhcE>747X1=gV$1%fZy*86 z0s`S-rTRRRJ>0`5=raa(87BpAp%qCfbbz*LMH;U#qRBDlg^;w{?1o144I#iTC|e*> zW709JFMa6Ev-JBv3mo;f-k*~fHooP%Ba<bUyL*uE)7*EbeW@kg&;EH<`2oQ@5$km4 zIfQ6OIfw_Af>EMSjkD!cQi|=Hnw0^GY$d_xd`9tnm9{-XVC`w7C<9Xiuu6&BM#o1^ z<ip8#YdO|Uf2ut5yIhg;{zck5^GH%{O%1Eq15PC;1z%mZTLLEc_*;W*FIz(1y<6Fw z2D4+dqzBW1a;9#kt=^>M%~0=sPKEE~FZ(EivYe}0z%2vCEdDr$iv(Q!KhW++2QISP z&eia)4y#5L>0WBLGOaHqUlA7&G8TEA(+b&0k>G;CK@iwjchq%-%+;c7vzXP6AU5vc zH3H=jU&Y!m4eoq*hyrGzk(l9ZYopK9eF_Y|Qf|t_oL{P&xx@;#KQ;;vkJ$AfX%t0q zyU)R`$C|x7=j(r|+$$U_BJ8iG>t<AaFBEEPNAW#>Aq{d>7HRdd7dNw0@f}hKYV~Q! zRok~jW|2s%;2xdktwwuT4?TCv*eIrT`S@TA{9)w^Yn?J=#dT_dgiU^&c{t1Cvhgxv zBxNV?+Z;`2HnhKSATI8%stJf$rI_JLZB$K$YBpg|wtKk;8@5iz*q<e<2WlWNik_gJ z2Q8dy4CgXvbaPP(d<BXm?+=@+ZU9GrsXd<CZq+j_O9E068?Y7=(lXG{$#*yhJ-7if zoVy<mg0(x~676kB5R`&xHEor5HM`83MiiJ+c?Q_mUUAGWc?@Ws#kOW0gHNhmjFUXP z6US~bb0vKE$t!!(Tem6cS5+p$z@?Z(K!{I!t!`+<h`BB%U160}IeJJ(mx{&Uue9Bt zQkcdf*4;Y<r6N~4$iQSI;;~4%y?IE(jf7gy{6bTyMhRO$wUQ>$+2mCe`K+eWWvZHo z9L_x+b-OQ8h&JVM*+kz%?5g8byCQ_>n)-YE>eakk1FYhOa6(dxzt^E-mPA*VeZNOy z*0xW<Igg<hZ%HEJyPe>guC1ieMt5}L?CV!>D^Pmm8uE$BRJWajL-1ZGuF11<SVeer zj1m7m<gxzlLlYA=6-IhyXWV#f&!2!o9??PYdR*S)i=B&|&Z*^g$D-g`Ld4DHem`V+ zD#Hix?7{v@b2J-|p2ynRi6K0&S@Q7vvuCEy`Sja-E2@~koX`|^{(2|r)6(4MI~GU2 z;F{`M+i_x$<*zU6CgdwaUd_f{9YaCk%Pn(UP$eE^RQ07g(yk3|4_G!^i{XjV&YvS0 zDAv%v;jk7RO`p6c|HT5>V<DiYT>g{%*#qz^sj2-N6M!SbH+s`7wP#SW+i-~?R-Z+~ zwp(#W;NwRntj)mCP?nVD_Tzb_u2ZLV6PORXtge_&){Wki1H+hM)y59Mg4^*Iuw9Uo zfWf3JFIkv1v^nG?C+`w{IC9=j^xwE{m{*kObX(SE9jW8zSDY?A_9;9)nj@ALy8{W0 zh_D&sn|&ZDRE$F!9Ub+8wibTYd=fwml@{t)hBGDRe|^;yX?~fR*@YnOOA?zzGQ+m# z`V%+4{k`?=j%2@(kWpOd6H_;-Y&jeDGA2gv%=?d!(-z$RPZqKEGPk{#yWh^##pXVx zQy<$~Mt)IJ!fcj)`+NEhZCjy>NoR|9o}b;b7fnq~QQYF%Q1GN3Nk7TXexWfc(fe^T z0$ly|<Kz57TTRo&gnWzRgV+(1mBqU<8aH2uPM3rJJmeElf1RTZv#J4EEIh<nO;)({ z5Bhb}(vR1>ARZTNRuQ=rzL%Qpgr$q>DZI+hiHMk!A&EJe)%`hGyEBKXr=>3~HD=*` zBjb2>5Fc3&co0<xbhjE$g+FodGa5|Of<l{lYevh4P_gBqSFl!ynVPw){=u5EfqwHg zB6Oy6#>h%HQE`aB=`!V*0Kj(p2<9v$_c-@}W+O4>00G+b7#XLX4s%em4bB*+k7}LF zNWc)+^6{eI+%uHWcQ%LHM2VV+nKx|?IwWGB6d(TJlhPxgP|*#e0z^!<pn+DKd@Uow z#;-RL*$PG{E6I}<6YE=U0Mp5*w{EO5(*|+V#W{}mH(=-~FX67u#Em>VF0%-gZl3Nf z{x*vz1|<D{VnjJ68keLM>R{U@^&C{7R$?;VQ}=s56z;FOL2}EDxB44|Zk#)}v9Qo) zRba5b>ez8j3XH5AB;mxpT+5YAUn}3p&CT<}%t(WIV4EMyOTO;FlTCFqA>c>6Wf7rX z9A%YOsK41ve|PtK@VX?fi^I!7Bae-mYfgmYUwxoe2Pvx>YFNbgd0;*(h4p7}n1(0Y z%m(7W3mulOu)F#UrkVKOSDEk1)qh`MCHnQwex@(J#qQF(aRK|TuS+w_##8@!Amin! zQjBC`-`|z71?0J?1S9L9cXh#wk9)AM;^JKS&wu69W1MQW+-~Ao4rRp%(y)EUGMTXp zSzFgl;%ZsNWwvK{Wv%ESI4ewWH|9><s_zXGU4B6!lbg4rf(qXR*KPJ_rb0@e21^>% zG%(P3@KNhp-iC$*{9&USC;s?+a@4e)(4#9EF6M8|D&ILNzIzI#c$;=faw!Yte=HY` zn8V$ZLPLhPVl@+Nd-}7<;Fl}r?xv9%>UYjf^-FxZaoYAqXMe>+Twd(WB~k#k+q^v8 zb*kzSpTH!4){BdD{ZC=mRX?h{iUedLI_(F(x`@9umb;MqMr4hSeR=N~hp+p%K9HKh z$X)|Io!$y%wfiILkyEeWEW@6kuM^I+9zdwEb95X^v#%H~d+AJ8Ur=`?*6)1&wU=Y* zJLqHMD*H3e$k%10DW~~z;N;h%Z)+OSFDFWP%6@~3Fg7DhoI=8*c%5;SFQCbv<Hhc7 z_uj3j3Gik!p169oFeT-@s;*>ky0|4cXj39l39+!9MOsM@l}WC`P>NoBj4|$@8@?Iw z-weg%y<HJ7P^4Fu_Mn=%4jtJu^zOJWXbJk%v?%(yco_UlMqRVaezd`ofrF)BclQO5 z^U}Km4)gB{o=|ikhcl2U6Eo>mYm<6s`I;G1KVDLpD`SY8j<fnU4>Up*Rs+UnY|X;N zgcQt9jV@kfakLuyay`vw3-6%htz}d)$|%#_r9ReND_YlA3iIq?!ZEgA;c#kYID*0L z4(g46MQNDo4o|mD`<(OmB4f^W%SkC{ChLa#QKe<(h|l%%U)?wHl`NDyBXv%9FxsCt z<-Hip8)wq&*ooWiHZ;a9O|L*V1T9;<$HYzV@>G_BEY0sIbEkHDQ^@A*T2JXh);CS< zK_MRgpE1o`=7d)m2S3RDQ+{qyz+-2Tde#81Sp`FUO$%1;xhk|H&{7H$qnaRj9y@Qo zmK}di(UB71BDX#@BJ+gp+8dzvvZv{6a685#BCI|VqZJ2O#nx5_uk*#r3u(dMsQ-*7 ztn-{X1Rlgb3*)UdMLw>D9{u!*!M!mE4Kov`)749ER!8DolMy%L3pJUw!8P7df`XF& z*biq4ccw4RM3h~zovM4rMc-T^4?hFVG!7SN#sI-}J9$%@pRXUJ9oQDw_qX}N!`Xw4 z7H`IMS%iqAsHpRUQnUK<c9XbQd$Q&oSMX25=5%9c(<ZCjvx5>g2mKB$Jq<5Is8h@i zO0Jmp8T4@|RI1MFA1oFNp+~3{ovrgX?;x?gAkv5C=)3>?2qczQpujd3GmVYki`bDB zUhAU;KQ4{u<^ru0wn(3=u6$Uf7QL>8`u-*Fap=I?F^o(vXWz+&Fr<r|9awkI-(%F; zzbma6knFyvM$Ow>nZ6Ft!_1Y1M`X44(vzqCvYT^)ogtOItCWP#+C{oGS%td?FRcX? z5%(7qKo-c=Y;y*>(6Tbn@6YAyV-c@D<{9wfYHd~q`bcE=+?N|+OoM4M74O&QP$;y& zrmCavN`fHrf>C|x=&ZT^W>O1MXKMlxxc6qJcDNtpJ(xBVOkrsCVezsmfWv!SVD_iG zYD3F0<~teVd7+h4F-*w$pvK^lymCF2>iLTsv6?TVZnIQN;6L}(Uh-#c6}nOLZgaG@ zC<A}|p&=9@YqsJH+0yq(iRLtvc6u>7n1r?8$0BN$glJ2*9;py#<@!p&^OnCskMHc5 zUFE)pi^@m)bsAe3_4C9-${f8Z#~^XI2y?y_GTuli^^6m?*&a{~Ie3b0k(T!5Rt6fb z1)Wi6=?b&H@rt*omLGd961FvtgId{gBXRM|qxEG%hiO*A^)t&8Cbb>MLR+bh82Sls zOmBJ0tywJH877|>yrU>81(#QBy^!qyY-efz$H}1M)&C}SoY*224{m}5cL;XXLxC%i zINKOY^nD`@^uYR<k+d@gvMUJUWb2;G@-IwbHva|hJYHM*@etMIxx`)7`<pyvE9qd} zevTa@*nn!S_no_QT8+Uxh%zoS*i8_i!Tt>#$RVJ!t)~=Az$T~ewB^4=TcPqoTR@gJ z_iyFr+iy)a*+<mV$pyF3WF+n_JX&7t+&)C{YYkPsjW0JrNVdw$$n2AMDon0YEirhh zO|SuWaZyM+TG3}MS)^J8K5kdT^s)?S?kn|FEBe|S|6a%##Y}V8&v*~q!bw5gwScY= z05TT0DxClPI2L#fh}=F?p`_fis!Ipb0}h6+<&O($<_TJTI`rq}%m9G>Jv-HqP4;eW znFyv-D>#Cq0ADC<N%I2evqh?a<9uR^pVIt@YHFIwmb*-2A5X}dmnElwXgs)pq||h- zz*rb+3Uv7hI~uV9`bgS`@)}fB;Dw*RsiYL(7hsbV$w0E_rJ+#=_=P_h1t=8JWU)&C zDgYo1lJ{Tyx6^-NT<)YVAaI^mpjKpk{c*|5<D<WTuLg9P9=02|r3V~`f$xaNuqKph z0M-FbSE)ciisU~(?tT)K(5HFGW~=+O3J}7b2eb(qz?)u(Y|t?dczPmz!T>a+SDY7U zwKl>SF9KEw|Nd1=TwJF!4CwkrOick$gGSq-kTfnxn)tqZ0GRN<c>!GZo&YWF#O2WS zT^l2(;)PYd_h-h=rKABiqYyfv6a&x<E@}hxUh<!Zxnpd^9TVsJUuf%R40O9NsiFeY zYu7~N<<lGniz(+HegPzZId(U!ti-lvQkRw@`E^rRE*HeaaNM=9xZzy?q67d#WFq?R zMQX&x^OSWIB9cXKMkt6p2Uhh90YGeck2+-V<<OILLF*)po#C52d5Mv{$t&&i$2=@l z94yOX%o+Pd#y<4&hVumGzd%@%KLCo=!(aeH@?(p7Fct9n{|%u7KKuVuF<=hx7|w<O zB^E$A2V^mEsB9f_p?^9TK%Sv#q@&_Uo4I@OcF(;7rCm^#33LFh4P>d{^x!r?O?q!0 z3C#ExB7^DD0Ijc#Xyq#_tAE=WS$w*>1OE&7fVlvw)y%85b|pnXy#lRwnje?3z?l+e zxq~;nS6`sD>d6Oi-_=TGfQPB$cha2mXV)i_44SJOl$rjirpd1x&}=gn&2rv_u%_%c zJWt!O2zWUIU+kex^(M?dUR!3XFqbAfbid3W@!Ki(wCzzfDlu4C<%SoHIC1%{U8<cH zDFXCW1)j@vaEgK>ddS=NU~P1ND3?|29(8~x5mXGgtHuYVVOm;$sG{UB9x1L;<sHK% zk0Cj7C36A_zY25{WjRLW!CrN^ox9W1`lPLZ?ygLjafKGyrvihJq1486QM?E(*2yrA zvp+%98+UdSn6fvtJ6zrin_RB*Ma;S2{wm`F2f069QfGTFB_*YL;&+T^fpW?D{eFGF zcaf+u4!rBgmpXr6xlYos%4t;$k;RMzZs1{B1@AWNUy0y%zB^hgLhsnDBy-c7lo{lH z)B^+zU@#bvRnwfRQ_oL33)3<8VQ4Dz1(a-d#}haLJrgIOaRok?D$0EG?3s@F*`Wgj zrd6`>r^9>C(S5Y(XA2=6pa8Gpu?hTq?SUjPaJ&m}x*BlC6mU~q4(CD2^wW^`HYM>c zO;#we#Be_AV6H4G&0BZ4*Kh5o{`B<pzn3fa@1a`{NpGtEidVmN5MXT$@Vo@R{7=CC zJV(9-c$H99w7~2CJbQD-Ur~KH3Uq#=ih;%!xFHLC(@T}h)-+Att`bvw0B%fm19<T! zNjQ?PR;9=3ewuywh@HgskaKhAwL%|P#EBPZRE)P2+lMb*2F7sd-`JsiymBkDgpDqK zS_JyJpF3ZH6#r6i&-TW{b*XewjatGJ^Q8HdEc<WPidNfVVz;Fw05Fhxs8tuQ7fjy7 z%t6;Dx=*cWq}cKT&D<M2CN07Mn`b;7z^GvePMe?TT3cVq8&IbM?y&)|MJO>f3LA0K z((02*6=ktIX~)~%pgGmSBYHkvzw!FNSiqzjKm4;;p<SOiFKObp+g~&}DZpiU*ft!v z+`koXNC27Dhe!Vse}9&8au+=5Y$y0CSUUE@x&QnZZ9_z`4&PY8gZ5TkokiA%Y<J+% z7{O-5x;*|$KT+rZ$pKTN?#^LW6~=MF=Mn_PoD;Q5o+1ViKLAcI_1uUXygOOL@?jkL zp-ICu{r@nVB)f)~2J7fw0I~{|qOOnchns5XWorBfs2;6}y7A%MLKyY#7YoOnB*Fzl z)4G9o3PqXOOI`{2`Sj{7ny=KHl$SEN!V#ihfMH9g>3>jfkULyG&tyhJbD1i`eLU?f zs1a{ku{3+_VJaWIFw)@)HI?61U8l(rsL6OEc?EF%kkqYs*)LAD>v-{?9>BTp?p*qD z6DO_>Fn3dw^!VZBRPcX<dc$b(J>TO1hs^{xyvtY26&l$mI<z#G9m?`vXrJ?7UyQi& zpCO%fe~B+r3Jd|nA1u(MATo2GDG=<0hJFJyf7R2vg`eTv?#LFG#0^F=T6rm4exHF( z&2xqFdV|&XfYV~3IQB3Gy4@xF#MG!;mKg{ejnwa+-QD%Tjh3`)<To!Sq^73&GIG@~ zBwL3wa5cSKjL{^z1ua#FA8yM3!A7?Z{r>$Knq!4Bn<%4?h>Mu3)hj$iBpd#&csG_# zPt|=-0!iB~U*i=4s~s#flmO^P0?Te*g|(lQ<NLQ-TYR_fS$hSa+W+}OkEApMC$y@~ zB_vz>nI+iR2krw)78*gVf@>1XkkOo~)jplZ(i_t8oYA65la=IF+s5F;kGMXq%?}}( zo1OIYQpbstXn)tGeQ*-d@|Y^;j_-Sb%@<Fr-=1mm?=eLWSL>`yU6?^?kr3`3E;BV= z9)sP?Dkn#wS1vX5C5c)4pFaKt2&iFg+ba`10GbY{rT{8?L4~Y}iayAuf=;w-@=KHZ zK)&N}x4bSOZ%zeo8Fl{J;U=eKNV;EQa*K@Nis<kiR|5u_TpI&<7uVhbsKtt)1h=AX z3T@fM){NKtxu>Q+Ns3~ceFc4%--KD3qw3HBj$h|a`|l7Shukgx2%p(ad~amH!c~77 zY=ZYMbJ%Rv`f<p~#J~>rQM|UxXx`ZDPVnmow_#`{AXNoY=WV~3WsA~orD7w6((@}D zKH1`=Ij1`Bqn2h^G|a$7d{|o;#k_;UFL~*8oXoUFdr{h6S8EldDPW;jb#BM79sZu$ z)9darS98S~4+6<a@c5_?XHw_5^TBIXOoU`l#CNG?0_@?;!=jn6;bYLy^%xZqMFRr^ z`(mb&iSI^aF3J0dr-Jk<O2iK9<cb^3jeTqnHctWFZjxwP?e?(s@=)fEs?&GM-Nj{o z-R<etokm{O3X6t?lTg3rXC!3SH;?L__cl7WWshx1AK}KLdqjMkcCB*ALAxL~6p*L@ z!V9tEKSRuDsFbia_ce}_!x=1id^(<fG~U)Bm{hVEOo;M@r^%B$``kHaYMZSxXMCAB zK(m96)`9KLiTy4nVqcZ3&)OTUJtx*H(TC&X2af#8d@&e+V?JMJA0C7TlJD1tfXlal zeP=jhjrY;9@ewB9SBP#RDRDHw+hsUQ3XO&@0V;>df0Y!H;3MUowzJrv%EG8lRlqm? zH)Us&(6a%z^FGKIbnlx7#EoZrtJbYPI~7J!BIvI|FL(WM1Xec`PdIZHX)ETgEOL8_ zrP=B9um*w;&(2cd<XNuP$Zwcb2V^%{a{gvM<!6w6pnQ=zt$z$k-^?Vg-!S#Css<C@ zp9Vbv{W>`c@oj5wFEzR@8QOnb5!v4$=;jbCTjc2%vYPEbhg2k?ChhGN!NVPVQJqD> z$JlY^c`iQ_Yk8%-x3;8~QvrK>`ygeUX6kOM_qqTCYhzj;fRe3s(DUDk@>(5Iy&vFL z?`(wKZKa<RynJ^IR-rZ|rpM8{e-Q@9X8_qw;2Jf$*;iLPazpW<e4+~eM|38CmD@<B zzIiiz+2x{Kp<%cT(fk!4I{4`+25ERHa6|gnW85SHm$)w0WHu4;tZxz~&}7B}X@qut z-MhqUuNe>!R#a5vCdw}B4#j`*jQWK`gsM`;^BV-EBqfDSD(`{a)htU4UXSrDHY@_V z{sb5>nWh6=Cd$Qt@wMvv(KZ|L>t%XckiW$9pIOveZRc-c*IDl-2jX8wT70yExua^D z1xDAUs%vHfH4ldxtm@4~nLp3nn^v~lwbXkauo&Bu6>E<{#X_!R1mPMo)ifhZOyQvx zgiPA)c8@zMQc{M%mH)drS@7)UYclg~w!ar-q1)x(_eV-t^$;S?_c4~arN)+SzWK&w z9F7T>{>)eWeYs#X*Cbb9w<L=swpXvSyttT^l?8(H84snqi<us|amAX%4f?9B2c{>u z@8jJ<Ebz)9+gf_jo`HRvW>W!I%r^-rSliJ6b8R6#y~%8wz4<DaZ_shqBxxm1p0u5N zh*Es_XGZmA!M!Xq!mRkO0CmsmtX-gb%3!1J{f&|KS@UADIphOBmlrzM&rk*auu{Oj zp>5~KBXR<S$a$mj{qrmU6qpAD>OQ_slg`J<wP3pk4{j9>rtpSuqI`F;lc$FV@knQn zsHzPh^z|_|4EpW88Qr^H<bo^);i6Q6g?M=<knLwb?lgbe$h{LSTx!^g>>ix0sj0!c zy|?%{x96tlgJrLrFcZhtTiz214SN~iwFSrsrue7Y9kb`|nfn3?la8ag-=X^*(Czh} zn`g)79qNDs11v8a4b1q*A49N@iQH7@q5rY{6uf^wM=LRB!etV%tri}mEU~Vh`o}e2 zBkC3yP_IG^RT8n&nXSZuqYZOU7bU;5&+gC^VZ|({!<!e_era4|@%8yq`-r~)T@X^y zurNrWz%!o?>{`U+mi~YVy4klHqt2-s#OJUyCLVghWNWOf)$NYtQ&rLc0+{m597^~T zguC~~pUOuRg=iNxK%a_h;R=3(K2?)Ma;bnfqZ;`U10q+N<N2bYWK>L&h-GYA+RbK1 zbWgUay8@?0#aD)Ipo(o6+}_wwIL#%``$IPh2WR8r#oNz*I!Zdk0D@ZijRF&NLw%sG zwl=!jOYaq-Z2@(*eHAW7w)6pXH#>8rD`e(Ztc2MeXfU2M2^6gV=}pt@<|zKeNw}Eh zbd6;UBuw?_SM7Ba3gwy)Xf?#Fc02oxT<)5A`>)0FV+A~2=9CwP0GT;ddLQEM4ii_& z<nRha_vOa|D&EcCv6d0610Zx|y_;l#LG$Zv5k0+OMS7+ka1YHi8TV^iOiZkynyTi` zm~L}Bw4!X6SP1UU!c-BBNtamyk*C#9w^6$z%cWtVh`PWf%-g8$nMhKoV|HNRFEv1F zUmlS8S2O15$WQ*y7;s)3KleIhvYpuTPdlYs+n0Hbd}Jv8z-b25**ypsCG{Dfi`IL( z>A(87yeanEu|q8u>W)FJ;a~*zVJx!D{zmp5DSVkbeY{Plk-0F~CL5S5IZpp$OxN}1 zDqqC?H9|*6){=v%$W7C!aU9{<+pw=zl}ioRuCqgSio@-*Hb<M)W|5*B<CXpguMY4m zWzk#_X@m*KCGJ}aZ=U;g1^^2&5>Sp=JRijGk2}?y`Ky$rOfIrgjjF`d3;sTP%|`P= zgW8pVf!J0LH>S)~xb(o06lK3D+M2W(-yiMCVwM4CP}mpfYhbbf3Ez3NDHa%L6C?1h z7fkx+I#^DDjC2qZ!*u(PJH8brKi|&wkd5km1^R4#pIR95=Kx$FIDe=YZmMHzy;%7? zm2XklTSnudMg?O&PH*tzVIm>7U`lQDH2r+}eHrGpgXATEi}8`#cF@_ao1E4F#(80V z`|q@Zk>`FyQN4mL7xWeY)v>^SIm>IC>L7s3{qT;Q(O)#``eyEYwi0O~0LpQnBwb)& z2Co4i1&=`O^AB@f>u<2K)*GZSYQzI1i)X=)1VtPGMm_V+83PTL3kW}7??QD-Ukb4% z%8MfYp~-n3XZ^yg-Za=WHuzkQ6Ln7_q2heGaan~oOT-;zVNC#siBC$*ufz1h1it{) z@4x@^xB53#Ui+)B@<*JcFSq}EsOCgtuN6IV51vwY8#j4lEBk+n_ZyI_Q|bo2cdJyw z85nY0zgcVIwV&np#-jBSpyzVrs7IF1y(c_-DwKE<^tSqyq?FW{l&IM4sPM7~04mT8 zFpi#o{pT}>iL5N?V%Pub?meTLYQJyMC?YB#qM{T51(6N{0zw2trAzM}=`A2N^r$rH zAiWES^bSD?)dJF`mrz7{OXw{m+y~#^d;aI#|NU^!mox6T`Q!-0oxPv3o;lZAb8cdQ zs_y*FPoW*<kLJ{hhC9j9;^TAsgyyMxGD>M9^jILjXDlA33{;X?njy8g4GgE|wPU&Q z*TE2<aS1?qINw?XE9J$~11zqqyHa48{;WW)C~hQi;8+`&3$lWS6CsdjsOq;z5(JaE zEn~mNZD0yrI#>pIwNKAK<vVi+(y^x*(=v;Z5FWOzX*G$KeB5rr$2R;dtCG$9*&+$s z8~xVo@}$5Y=6rO59J$gtBVRnNow2x=8@D0y0cSkV!jTl_{kVK@<d<G-#1B)xR8N(M zZ}eFXE*N0cJLuU;uK_<bpzeBldCbqQ^NU>((bA$3=lH$$3BviOE0t0P`p}!+>Nm4_ zA(I!*2xpSm#jwfB@Vm$7zwE8^jS+y!tJ>Kh$&H?pv0NnjLiHX6QPDd?q_&4KKt-jJ z03HIY?*Bv1nP)Ce48FK;%H%1o*C3tgVk9}I2P;wFTjw)rRvQDYgxsRscviplSGtik zGz#aUEU~z__y95~58Y?LTVfpdr-3pg)5p4;O|!2-(CjOo=<7SqFtm!PTIYv&1mhF; ziLP%YhG9XCDCB>7n?CBK2;1==O=(wE?G=p{BjDS4m2-cEa}E`uP1D%M!|11ye|R4V z7t;y-!7y0$vHI-}pM0LJ;W)wpQayV%SX|Ra8R<}DEIn}INQpJmsXw8hd6VaRv=KU^ z?KnfTV%+ce^EUx-rf~3PXCuRtwa_xF-suWEWqEnqs`qUQw|tC&EEi95wRwlou3w7{ z4TBoj*#4?DMobh+z4-C5eogyyqFz%#L|eAx5~E|8FKVO<1AJ9zA9}cM2eQ?*&_kJx zJ@Ji(F*uInqv_$YQT;Lr?CL5<-pb(V^zojpaeZ8+<<tytK|0(yF#ha!oNM6t=f|hW z4Sl%vj5K7SKW15AC$S`~*@G_8tMuPo0DHA<E-rZgO0kaQRiD)e8(4}UAQ1+7(u>f( zD8pUc48jqeH&{P8-q^Dqh*0uAkh>ir3VcL>j;Pbdhtq?6b+AItKV`7#RFH*xuCZ^a zlZ9+$v`(gdalfWJsZoP43SV(f5oc}h(G+&~Fm_ai=X6wW){>J%Wz?r77?oN6{6#P{ z7gz4<PB~aJb}mvI6tG~~Pruz}Yxt;{P{F=!vKts+e*1Qh*#U*@OQnz}*Y@Uh?2e9e z51I?qI!^m>BtJ27j!K!2EmdTPTh`#KlYwEed3|>Ds=WIK?}AZi|8HIhsN(`?h(6Du zWj$V<0b`~cDR5Yeb}7&(wD*lX1B+)-?0cMi?hn%#df-CBD|R|xV1^Puoo{RwhI!9z zP|UYfSMLJntSDN(`T73z_NaAg+;7>yXZM1YeU2AwHT{L~H79@Sau}OVgNl@vW@iCz z`9711?5qG_H^$}&7*v6z-_L2q)Y=+Hv^;?z?!9ktFzpW%zf$y*qH9IUeo0loD?)fD zYUjHgEk_HfkXE<}fV1#U7~a07%{G_5C+;Vk`}-Suh{LT%=i;E3Z}TGdH~w5Lq4nKd z^+=|ZNa9n@G%JtzAi0$+6V$m2lrBx6YrNCssQd7hOjP?=TgppIU1p$RIU*=f9IQ+n zgZ|h|ac{}bDl~S+K0e=t8K2B0`N1OTqiBRHXMjKp^n^<-z7qLv&yPZ_WKavpUe#ca zm*B-KTs3I6DC=(FePU_&GlzmKZia%jFKwoB?bnz$&QEM%<y7q8qcJhoOVD3AO`Nq^ zm?!TuUBu#C-rNfZB9NQ8_eK8cD`<Rj{6^uRxbW;WP-h5Q^{)c?jidNZix^(CZ2U`? z6Lin(Tl>t(K((YIjX%ifpS@z3Eg68w-XqUfeGG=?2Vs4R_A{mNiHTNG4(aAc2RO#1 zj<>gCKagL1z7z@b^=T*yz9{j;GA_K#`3u*H^ds0E;AilRI1}i&9nqfy?~G*`u0^CG zJAfHnEhr7@f%N1!?w}1uKT(@NFVLLI?6Mu+m#0y@%o^OVGos+k9TIXIMmN}?J9j(7 z_pA0uRl}2oxaU}!&ELM?7Pd3}wqt_!)~@{Chx3Un04-{Dqx-s8y&0C0Mg~<AwfWj5 zo>)hubvAzx$?om$kfl{%-d>7tudlJAy>)#ayX0#X)$sXRo@rl~DR$W#v$^})Zm`&I za8nQM&dB36T*AES?Og}_c&79^4mn60D$~$qGUzv<;+4;!`Hp#Jti>yO4`z46xneu+ zqrSc4>{6>zEBMJrZNaT~DKLD}*$W}T7$NPuXzw!=GIm_^{Di}!<2*KI&dN4WE1vad z=vYD1RLKm{b^scKS7pXx?veKH*#)}Vpog5GOI$`9OKbSepMJpelKBk2nChY&aykXe z>Idq~P=LxBxZ6)xKEI_0$4q+@d>UXHmX@E%8tI|cHt0I+@3-)vz(8R9BlhUNPGmjs z+5^T(Rr_VlYa=Brb53E4Bbnv=gJ<X%m|zWl-l&8^q56xnK>zJHQHhVx&WyitN5ZKP zSmYpu`qZ#M<lfxhu6%^kj-v;JsM7Hj@cXM=x5sWwmkd%!^xkfP@yrOe0HNgN&7BIj zu4Oj=<%Kv`{j%D;;F9MLG?SfSQyCafdiDYus)i@9%eo%~GL&UGyoMPto~KhbL$=!K zVO%Yp+gPA^h?H(OkbT(+BW8MO>5}NKsQkm|@Gv!yMpqZcL`%jv*68(mVW0ck?UF-W zm}BBl3C)wmX>n(7^Y)4x6_@1_*M{;J7Ym{8+v^<yOSXZUH8AuZl;yOu<58Wqrn!X` zo#F$UPq4uY^T=Z)^p5XwOg|hTIzE-Ngt^UhamR#a7{Lg|#fN#n9xY`^qlmjoEe>)T zsUzHdazNX3tSzFe<nZlBe3u_#hK_gDwVrW_Fz``MMt3TnXvSyb$kV0iDmgj9du~LC z9_O>$K_*gen3}+k$4u71d+M8^Zq}Rvn}7k<+M`)W6_KR=EW`1c>mN2~&7j^7oA`4p z%h}&<OD34d^5jl<TlkK(qnT$OWnfC3zm!1vRw{kl6bfvRVYltS`8GIPY22(Y{mw}m z6C%n9Z3kKeEDUF1>-$;<+c%SO2tp3JZMB-`1_rf;PXtGNA!7b5+tCLiY+cphS}ewU zB_u}oIL!i~*L==4STjPDhSPY@Ht;9e9bbN-<Dv0-LGar6JOek;BjW?00Kr{|G(#c7 zqFF3Jmi^*cIyr3PfzHMRa(Az%sBwL)uDv;X=Hpv<=lQbke1PApmAP`T`IHA|`xq@$ zYCoR6;>by;CdUpI;egMEUXyvRLZ9-9ML)mD_ix_}FHViSEav6rcCn+84wO{Bru}~w zfUzEuWem7PV#mGRIw~i)PDcuL@yFc7ngaHxgd#=J0}S-;8|OUDK`(ycwhV>1DWLs` zB_0D%Wk+!^+i6M>dewZ*#eBjLv~<Eu_H5&QpNNet?LT;sd*`uNrQ*)TN_5vZvDrVl z=SOQ?urUA@sSoh`>1@hGMBk-JT3F%GO(HMRk}Dbps=OJY@vT88e#Wj&SWV}-?V)(- zlKSZAJDJvomJ3gfc`1ZNWZlsmEg~Y)b$4jq07a%!*Uu{@T6K@1xhn51BUX#`nxE_N zl8rb{d-fovEUU)N`9Up|PlXY<kq)sn_)XT40tYwS$^6!V1%%JxmMeM_L|z^oZ1|@) zsQ+j!46RVpSO=3KkxD-Mfx_l8z#_?FWD4mE2Ub7m-3>G{<#;6#03{~GQ@ff806`GK z-WlB;$O<zI<GuE?{HC%PAJ6e&0k9uCAgF$`-^knn1pQ}wrdNeao;!SRP0+jd9Tn%O z#uc(_Pmlg!mv+7<NeUp0?y1|!#<y6tQ^Y0<%sfh;c+}*<zEF4SYw;)rXdo^0=U>$n zXL3-*woUD9G{lo?+i#Zq7PQo16hWnZ(N0XL@-yOFTbkLKA{K><C?*t7-q+SZN!Q3= zstDSXKrC5;qQGNg8O6jVpA}x5co6G!u;ZBclgvIn*KJOE&n_*padU5S#{S0=ZI&d( z)iq`)0EnF>`I?QPK#GRb+}wW+kJs1iPNx;j51u^Ke$c1JMzv=flqT-oD!v-!1~tzv zU0wsG%);3}yN5_2Y{ur}?f)84#03SnDU05?fdlo+c{;+*2@B&(JQHjCUD7&bal+<% z9D|%FV%=H3bId7r1U7cxS)UhV0LOOw@_t6DQoPhTS;LNxj~I6tiq@R^Em~<7UF^VU z7LD$$Ah0CZW<Hxg9^<%ul=_T^cW02G0`SDM(C-2UNX8yV2-LxPEb$VExXnisE`Zug zJ%a5v3ztpboY<$nulwo4O1*cj(oA0l+!`x=?r{YWls6wbgf!MUZ}lWKR$E%fuQ`%$ zko_x(xe01t3uNc0pNeR#o}+GoeYfWSkrqKgdYM1D+qln=N1+Uu7F+{u4-Wv9O+2fC zLOQ9cI!<O2Ny(&hQj2()8WQkwvVfWV!?rKJNd>mfpylBIO%3nA5g9hYTVYa=r#9-F zPnbX})L$lq{IYg<8{$B(Pv(9CW`X@LZCVH&>q-zv<O-n)4Zow1p!(_&_>-G$?rTl` zNyD22OoEEBl%``~IuMbZnMcXDBzP-Yc4d(+{&cx=JO0uZC1re%1MTeR3Tq9ZTS;%d z&riN>5u%`8EOGDUErqJPE_iwQ_z%=%5SE<(Og`7GBO=v*Dwe8V=DUI^-R-<7@C>SL zE2|=o#lq?H5Q2r3`j7o8`KliSl_~ivc^V6gCaPZl4z%nDq?7TiPQ}HKPw5%iMG@kd zvOnaivZO$X4jnc2u4}HP9;9g(OBASFQI7}({T6B@K#c-qJ1r681CFlQHg@5aa7CAo z!GK$94R$8TGbFg>#Y<NeC_!jIUO);MN=sE59;8NF9^<Mjy{T7jKh;$oSKqxP;3%3C zt}HO#1iBjjLbEUDj#39q9+ik?EnhR;hXc;({*yO%zje`#^(H8e0h%j(*5;Bu7RnLK zlf=3FB0mbqKt6jB&*t6$e`f-X9%pXDXQ>E6W+U#+hn)KB;(m5s?*`Zuav-v@Y><TL z07?<+3)JC?<$P_$t>1s3dTk<{4()l3&|FIErZ>`wsOh{bApQa8VkPgsyKqTP_L)fd zK(}mcebcDnO5<wb>S{&;&Cuyg|H`1@)ZX!aQIQC?cOzwDsvkdD-(B3##d&U5O+aS6 zudHZ%Sn>wAnUCO(b(EkM<F~wDGtx|ILq5W3N0e#liWl_lQ(7R=;peF#AIz)91)=M{ z7b`zr_*E1Es~)YL_cOl?xywNO5c1vGi=CT(<UQeL3&cxs&&(F*kOgYx9c*vT-3_tL z%Rm2x@wOt#FVtRHEK*4nOyq_C{3jI3mX_`U#H;EYdA6TUKL7;gj|3qf-#h5@pCGS2 z#cE~7^cJ%~-n=MYf!s|Ql*&px{~H#-XaMf%DlozZIW-Oypgsei!t>ygf4_JQ{<2P~ zU+drv92j@eP*G7Mqg~s+T25P(7jg-$U2Kq>y>Ya!miLD{jR^uNhv$ID@5JH0MKL^@ z%eSeD#1o?YK3L)=YSt_neYYvwGn_F;iOYz_&qEulO%vJ3BCF97edB%ig?h&6&B)~T zd}~Ip`HBpCUN?^ELUxe)9DV}^j03AbL%^m(^>?8iXoT}%UjmQ!^QpQ6#~p^GQ<ZTv z7Ii9FBQ>SkH|^2I+d16?L+?8pxs=rBylYs5&hUDB)2MH0DT3C5{EKN|EWs!LiYwr6 z+fBy{eZoRle_`)Co{mx7W_;*`a9gTYUrO^CxNPBwIACWjM65=7?Hlu=^4B-~Nhppd zhK*I=8qwN_l`0EIUbBDivc=ZU?lxo$HHLIW4~$QXyISYObs;zGh0xQ}e(0UO#NN&; zkX?sbac!55?#Q5^pp+EauF6jaJw4DhT3Y^uT@y0Ma_-@kNB@lbf18N^_Qh+O`w%uJ zGr8M{3$#zMBjem|f=CGDx+=)|JX&I6T193Fp^DTYABFAK;>44SPyH1j5LK{-KE7{C zwx=V0_-0xWpR}sa3VCA=7TUG0PP&27=^=S2@zb);elN*a1~*QIu0x)ZT`_^ks;R42 zIhu`zZg8=k+qGS7XYZC8`!^RbMDRxqM{%8le4op^|9A1?$dMCJlu!+LUL`ucPdQa~ z8acUFOu$l&KAX!A`NQTgnB~O#$h^@_q8m?n_vTkqR+ypu$z;R4ko#t0B#?@<tFK|O zjl<STRF2)&1FZyzS9+qbUb>+nj)HRUTd2#Mb9ZUL61}ggAUAxWw5$y3Y>%t5GdBx^ zJjG`Dv5D1cMbe^BrKOQr3D6b*o-g{l<9T87?gkh0wT1;j{Ryy5i1y~Jr|ATP1#5)J zi*xa$5F0SYPbqCBlRL~;*Vb60y1VZ*?M#oB)If<L(|UI9?~k2am*1zyO3(W}bpp}< zlPV|JOuZ>;+&px&v{$KL9TcIrqJ)nr)-sbv%!SFXK@M5bznFMo<;ZGxMhT?b?r*QX zoQ?uNb}$QUMI|MM5EHB^dH|+i<&FE56`^(y3A3si$6zp#k&#my^kohov}^aU>WMVM zV+`p0Lemvv-Ws2xi!BVl#<^kXb%-LkP7zWc1x4jubx0R|cKm1v+Bnmhfk#g^R4Gvn zC#a|pjp4vUjgfAIoPR7~A6fVoRbMZ<qvcpTiX0NE9o@k6x|-Y7!#GSJ@rf0cdxGQQ z$)&Hz)6M9r%r43s8i6^;SjMY?D}-%PSwm#IPI;<rk@@;H$7y4~#Qc&9-=otB<&c() z*<B9-5jUEs1xm<MQ83&p=dDT#It{H@R)-ox>zR*kRY0ERim;TrXbR<B|GZ25X8UZc zJ5Km@wqZ0`yTs*7M;7G>aCB0ES&b`rO(Rc3W}^IYlK72$76>Szug-Fgzf!^CKHDDu z6`uavuK3^kLL(f8J`ahv)J=duINpM_**PDFI_8F~izv|)&wvA2>Kr(EtHdapUQHd; z?{(YSZ77Y8ad6XPNdIVkwnc47tUbB3VN&E)vY&G3#B^~OUIl~s+CO;}vLLoU&9&Qh z<m*2wyZ*S9F1U^IU@CU$-XG%lOcFVs8`)8TM-}ps_jX_Skjd?+xRZ%7-aA$w<D*K1 zy1zii#i}kg5qy{8iFG$5CYS|u|MZ75+~)~8RADk(*0-X-vMLf4uPE+2%~b_sV{x0{ z-70Zk{}X`n-R*0+(>FGr2MftdiMTmh)%v1cMg%EFi>I<m>}vE~+}1QN)miPtzp09> zjkwb4S^rzt_i>(kgl7b@Y^zxHrWdovT!3`G30dz_M7}JGW$F4m(bh=D))K#L8QCYd z7B5Xdri!{}bHOofTV`{+6K^QJnKB<ffIPUL!z8opDIL%EZbHpWbKW|LUqDk@ilHaN zDy~VyFCYfLFhkLo;quWib)4DawaZR#eNa$1OFRQLWZDa?nf+(p`4BCI%~H&IdYEk! z;bTp;h+l-hbRxU0V48ctug4W#-g7xR{R)n?jqx)k`yMRW36b2C_NdQotNl-`Gg&q+ z5zjU3wP!E!#qm*W4Qo$OdJ)*)QSJ&0c{j}BRF7^Iz9lufajfF)#n9Q^@#}GXSN_)X z`Pg79<#cnaq0`#Xgu=7iFa@ujk(JbJ9xpERD^b9H@Y%CVb<1fssCD6#Mo+bm3pWSt zVwOhAkf=9;2UL#63g}U4*3$s>^w51ZSlUSy<}ngk=Dqgf&(L*BoM?Oy^jXvXro<<> zM02k@Fx(U1tT$$zc=BF|BD=@mdgtRXE1?8g#urJoelz^8YOcB@*$459E5v*{>^qXC z0`jO#(}1tt)iuSDKiQT>dTe7<l#XdwB&9<VRC`e}7I81!-r%2*@ee*`YGn;imE*kW zvFR-5^oRvx-S*@6l-_0LIBNe4RnC-!t{qzj1D;ix8;BqXL@k?$%!F5C>uR|=o?uh4 zmj$&Acl(SK8t#H}THcupOh5+h!<SY29%|NduiP=g4koy@eJ(-#7(D8Q8Q|D#7&<Yc zZGBeH$kYyRiAA+uQPwXkHOpPhQp;r84cKC5;^#QkgNnv;zP?)SGK$BCoo-!WMQ+Oo z?7pbTVL#oaw&gmSW}nJaPDtt8d%JQLU(VnPQ`(N7v5uqhZu=xt(|ktO1z!{NM!XHZ zP?`2(gSvBVPXS{!gR|?YP&WFO>nazNTGEbHH{q6l?vHB>?E`Im`+K5p(<r+sp;&Fy z;f_kfAQf%9uctXyqucD&kI3KO?;c-@4{cSV^IxWKX6W(H@X>RpnUQDX%ejB3^mbHk zU*_R)EmBzN(9AAbS4+AMT|?b{wS0@5bmQQX1*2j}FPq%s6r{%Ei$(L)mAXfrC*9fP z7~{nokf%)GkYsyS!VaykmvFY)C@p>GxbMZO0heUZYp$OpIZEH9tfp_!&nt>Y4Dc?V zP;$X86urKHMhQwkyghfc;ADM4KdkkZqcIkjZzQ?mtAVcLJ`pdhDudGro~@GaJ)b-T z*t6t6E?+WbAVqylSWf4Yl`{RcBZ(vSr;`hz(Tvun6tPKWmUUH1N!CqLRg;&N@|6*N z^?>CVVSTwh+a%z9j!nGKI|Qeio-~W^6$N*AN8-|`yStk@2*dxJk2Zyq^&c1GdzdJf z^hIlpY84wCQfsTkKa9+VV1_)GfJ5QLC>E)kMP2P@SRo*kE}kl(^rE)JgF<Ac%%y48 z;3QlT=IL14QRB&$*74n_9zMgJbH9+bzb#}PxgyoBPvw8J?QRU;7$4u*^@glOiq=b( z(!`lRc=+EDEZO<xn;B0_F5pK(G57L%@{?TN%vq{+=h(0?^!#41pjSl2QFFNjTsMnO zqBfdgLg<+HgPy<azlc~*(P1VY-}aoQ7EZgm`NdAX4E?~UwrAEYKaE7MqS$Tq`=szP zzYXqq3i+C>Uu^!TgT6RN*}!YFUwV2DnTpj8sR5#Hn8yxemV(!5S({}kw9gabyms!Y zsa&T1W*z@VBvM~miZN{Su{5`;imcvolOLITpJ_A`-?D#l^Zm;|On!VfvEdayoj_)& zy7!0k%UrUEyKf$MJDSQoFo8nteopablb*DkkMxhd=)rFDiX57ThT<F>OQ}L?Q~oMh zGIurUsZH07$f*-SE(qH@a0r=-Y{a3A3dV<4xgU2e(|A#&@#y~Itm+Xjzqy8_k|5E! z?}PjlaI2p{liI%o@lLNFva%1-H)|7VF&(L8Xg0acH@Y>UCs7C2`&v={6(>JSCmnYr z7S@VljBLHr{^2{2t@|5Zdp;{nx*P87E}yP?Jyvy=a<aIbG_UDX_=jbeH!MH%zc5tt zZCUhug1<10{2Y(5mY3tx;iJiQ<mqPPWDi<2<ag91R|L>d8k~@FmE-H*IK#JFb(+0u z<qRKS-=bHxp)J{aZ=d7!V*vjE>jmZ*@X6)Pls<GZ9K2)xUiGBAV@}`L?!lhJx0--9 z-dEwa_npP(q@(jG@Ap(Zl!^XM{pp1IKL16Rv}H>9B$6VNfc)?)4(aLgeQf%nnTb5t zU2auy%UQ)MM5|u+y;^>OM)QwI3*YH;=BUpv7)V3LYJc{}<x_r&D;^O}%uwYm?!*5C zX!Z%%sP7kc)wCS?xF1gCqO`3&rnu+)Ue@;Le)Kx<chL7R=IcGG-g|uF8@L9!%@CI4 z)VY2*9)dqXIFo60`b3X~4`A}B@6`q0iBat>NgZa&N}CsL=`D$W_$-8PEQGIkOq%3B zB=CM=dZlwJ@-Q*LA=M+D2eGiK<)f7N@?GUKw05_z|J5ih?&G=n%u8@|@jyLHZV7_o zdy!7GJ{2WlroJ}1ka!e0C4|t#Bx+AQp$>F+Jsn|W_2J5Hqm20;nMq~XsmXMa6Go=? z^~wDue**hiMEOp6MPRV+N=P@`7C*-yGuBK^j8)wGn-U~r^zB^cjmFO|Wd6t;2}r&f zv)s)kk>jOEmI~JeEa)$kK4YD0`pyUV4<ld}Ifp)FWF6c+y<SShb<9hC-{2+jPVz6P zqUbFUfH>VRG=C)3W(I*D#8T@YmVJwo%HrIpa7UK7B-QIac~8GweLdM<o%@wfM=bP} zOVO`0Dtd+rUwCU{MhtByRPi(jNgh?P=VvSBiu9#DN2xDY%Ox_ePs+DG?q`1Q$;E_% zH9ovpAWP;Y{yu@qh*3wrBmZ5)AJG;yqf;UNi>S14`Qw{<WKigZe;9SPh~drV#Op3i zxh`vN=ojE>ujzlA^2xp;yG}zidTvCZb5XQqR+%zhH46~lH`mTs&5w=rAkin7iJ>)Q z$SBOkgPvpQtrnMEx+vd4&5hnMFt5iI(Me{M4DI<WSFXq@%#230@<;^nGB7J6ie<@; zO%{3Op7Fh=HxbJL4;;h554lVJ_qv9!6Z!>q86F!}e$=*1^)+E1fz64wSll7yY$&_g z^y)>j@spsct8pj^CPyEo1ccI}5!-0mI;N&n%4qs`#I;bcOHUU<D5Gh<g=I!NX573D zo?xF~Wkd4N;X!QF=K!k#>LGdfqufu_#Ytd&-!`{QIs0+l5^%6@c9EFjSEs;(Nw1CS zC)TI)G!t>@F+Lt@?wLANa6|;SYQuW4bO35Fe?na?x8SOsLdT7sD=nz_I-D*Mixi2y zyh9`W?nT61o5(<sclX=wR`4}|E>^F|6AW>kU4^}MjGr67RW`J@EF;mFn~W)>WPeUP zkjDSCGvAYKL$oEqn8z;bQzsj`JO3x;iva_YvC!q21b2E9X4VLC_lleXa@~iq(@$=9 zv9WtSVzIrz&Qi43L!8N?D18p{;f%d<NIvf}Y{4n?|87FVNMZ2AGEAq?vEW1#vLph# z+3tk$G-COvd5KW6ph?=c{}H{2q1&Q`8PDnZu5h=GC;c$JMAiGJ$6q8<>x)*%)yZ@c z$F$7~wtTvcq%Gzsn-#u00!ky8=%`b{_p`l;Q@+g}X+FPAlJSF+_*wh{Dz7u-#-|*8 zj4_8loim&V=i-n=uH7Nb5O?r0VI{``uT;09Tepz>o_g}}jz~F%V@)5CI5@<U<BXa- zDW0rv=_A3b$0e#d=^Eb8nQYkZ>yIB0=xi3%_S7|urjjgVxzm&Xck`)}0pjgLObbzS z!X<q81-nr;xKQ#U;%dD1W!#id7j^s8DCg66dQyz6r!T&sEp=n2gy6h6Z@@ZgGeMRF zYRZ%KpqzhfWSND2e?4Wx?^12xo|xvEY7P_eN~3I%5?ELyb{-?I`*Vb5?UGca<h!tw zAd$kDe6LoVvkLJ6y^s>S&_c-sW~ciKtn+%5rG{lfrfMVmMaWbBziXJ+CStFs_L*Da z!u3g8`^!eXZf~ScbKeGp6_|*_pc<nz%-<PE`Jw`;aLQtn|K<Xq?~H#17RB7;n>?)M z(qX1BNW*z+BN|o2eXGeZHzHr@%uEXBA~utrd@&fYPwbmwM%9N?`{CZ5h@6tIVhPXP z*AvFgm`yK<mu+lp%R406G48oLE&UBHC*tLC8LHF1!Z}>N0e3mUh6^++_o5|&{Ec3p z{_qHH<P*Z1?<VGhz}2$hO3WN^T<cI9u?AUV?B(LRE^}>1=LM*5-24{lE;6UhkN2v> zsOr{fiVH!tFDujBA}f&=H_>s3S{kimc13SbSTY9<L*La-O+H{gmid0|MQU@irB%7N zL*h9?-ao)=h9p6azwmAuh&6Ge%@N_PJgh~rU?=>0OQs_D{GSXlGq4-8AZ!Vap3|aQ zr|0cxp5m67xsP96ct=AVBdgZVYiGwNYfk2Vt;*>kjGMZY$euy*G9z)(?<CtDmBZ~1 z0-e;Xiew2Q;Czww0efWNEWxNTIc<~wZLU<Y5u7bkLcyb1&J5N+Dn|-#xEMoc!xc-3 ziWx~mL~hMGh3heCAP;K-EZR&GZ+%<TvRR8JxV1f2s9nVL(ym1&RG4>Ox?FL8SK)&S znO3=o#uqyZ`^)&!)^+>?i@2R2J{p$GGz-M$#Ja6+Z$QBL2eSBp#p`;=`3n*)ud+HZ zv8}(>J?7q8#dGi$Ek}APrti2_3a`%&UTd9seKn$d=`^zXW>cL+?1Yl3)KIBud$$Ym zS!@wjD&X^3`C=f6dfkX~nikLv+K&y^qSow5&`QRo_+XYNXTtnou|BhpX(qIlN#@jF z1X}E0#B=076!3o>D<F@ajtvjJleI*n*>=fD%>Jd0E{*2HSa$x-J9$fdFYJ{>-u;ic z_;c%SRz?=p)GVK#>$L62hU*c%Nkur*ODuB1;Y2A>;#mwpD9|xsYRPQr-=ZxO=(e-C z2bO7XCXaUJ-8qsj<W!|cj)ALH=_ENhe%yN=m)FH@rEq#YIRo>v*Tl@fLfn~Cmvtp; zUCj54-hA@HA`ZS5q2eVyZYeDldH1W^8)nn@cUVP=esq&uMqDY<IotkP`uZqH%d`~t z4HRs}-jPwXTD3EGWOCdd+1S{4T8j&5PC6($!%x5F1d*sNZMX#;9z?pF)FlLi0{UPB z96d4bl@fDH`W4oYbvwT!ZiwF^cALvsKHTHF=!W_|Gk=?OK_)8k_H$?PW#+$8X{xA< z=+246K%{K6?PVDO4uP(|{OOzDcC@PnA3Yk%H<r{TrpeU%mz#@G6G6P#&k}c`=-Rk{ za10*`rZl{y`PHwshri9~fL!#+#MeO(rLtew_EgHRB6FLsmgi#ErVdQmoas%vmPJGV zGF@b3;zzE5=K>=$T9Xy;X=O+iXSBPiUod8=@p;s?M<n`7cTSacZu)H(<e1COOuB_X zQ|mjs?aZ$O(&h4ZvK|U>a|<x@%gfhYeeEh8BD)IUnGfeZpBD`zKO+C<=ta163-akt zVl{F(3_%H##1tF&?-g|={9XXD?(qL9fI*+R(yPT#Hyps$;y9<-d3_8BC%pWhRePHa zg=%hv-=#_>5j(10-^R75c}aU7OodnOQobt&tAr}Db=Br<9?vq#?}Ngt;ob_0&_anh z-k&UmR?8&{Q%c$opXCG6<#Dl0H`|8!h3lJ|NaGWc7Wdgp?yC%ygJoSaw@|i4PFKs> zJs(6UCB8Ut%=u5CBGmCcsnWBYx)%4af9Eb$h$Nz5pY&ogg@Kwh)x72lCB4N<Og5!N zX|7jaSA=sM-7yKlGtxq1e@L^{A?RrQ4VBJ#9(PdN#dDDRIUwcT-*7{>M3pE#7{vUf zHHbFTMfPw>CbM!g({dJ%=cPn<1Y%7Y6gju7FY@xPSx@ACH%UHA9o)AZ+H4}w9D)Q{ z_F2k~QW5Xc_40qBS`M{67R8cvSIfIHNn+l~w@^zLUFde2<mWin325iD4SDx$VBlcS ztlayS|IH9d#_kgqIkt<O4=yTri;SLA5{Lzpx4B&z`8k=Y!1R4bHxu8O6u+$p5YgU0 zt0d@XP49j#J|yks_X?UO%2T+t+i71#!S$ma_M`9It;?C*>{(LPh&Ibzkrtyoo;{k> z*^wkLnn`=GheZxCET*M%H8p-}wI!d-UIiY+e-#dA+W|KMm$>6E63G&>qw?e;>Tb|i z$6L!Z3+r*BIrksrd}aQk`|7rmuN(~vAXk80IEk5?{pm`P!@mE1N0D=?KkkERxhcQ= zj6ssBRt)t6iVb%^TTr>Vc$SVXC@2u|H4V02@UQUObBL{fIZaeMxB!16wMG<tT+2i` zg}e^7pRQirU+h%}IFPy#$zuZr>yztk`O-{F0uC2KXe9^kkh)xu3zzJS0u%JGJ%k}E zz_a{{^!v{I-y`D%yWy2RAnZxr?{RyRb6lHiNHX?QE+@)x-P*R6SYU-d#*~Id3Xq_P zySe5yO?A8BDFRK;{~<qq^5>s&<t6f4Jv->`N1|L0u8^0ZEGDB?kRqfSSifH|QY>ei zNT&O>VEXkUfTHggQgGRL{nXl?i4j`W_uXQEw|$09UR;8#IXdthhV43*{QhRMb9ds2 zbvu(~8b8#}-BjW<^NxQ!KjVF&oN64qv@GVHjCq{Pnq`eF8*6MYM=d@rypxT+`;6hk zTpqAEJt$H6-P*<Sqd)FT4$W<UxE)KliYb}951D!pdN8$8tO4ZN*Z2NI^hYfDjUSsP zZg3+Thpk?L424o+v$I=b*8jq)kO6z`9TJonq;9Sodcc@uY-|+8h-mvfQr5-0LyCeC zoTqrpsy!cG*;po&NGKiZyog|C3Za+atdA>tL8di9{VBjxwilo{orz0YA<Q=|Edi7G z8U8^H?2&X;rVq!WEtdDz@Yk6Rxd_z{x<9>tVP$zlGYmETvL0=i{4!|u_CGL|E04%P zU*G%qQ)zNeI``u7uzuEqJ+elUqeKw4`w=#GXH7Yh`U0j4PS*n8o84QiC^wbaB&~lf zIIu&#Ud{Jj%YNUUVRwG8vg=UmSqKeFEBs2p8l;_GQJ>?EzosP^pmEmlH8qLi$AEDD z1<u!N;xrV0AJ^A2W)2M0mYU#KOH^QG5;xAc$d~j|lK_=dtxua|MpZ8crQa_@?C6^h zUEXYbrCc8pGtV<lZ07bG9{K8d4aQ(<HL&a&_*I!fvfGSu9JUttU)iRAK@XGj0Kd#N zITw19kkxyMuJk53MD{-4vQ7Rg<MaVn3$i^6<{ATe<<6rVrxSeKZf<Tqr!<hzY_PNM z``z$pvp!l`K^6ZGkOWT2upf89ubGn>km&TWrLIAOC8P*iCG$;A25Uo<w>FF+vi=}L z5~@Pl`SO9fu`yzQsMI)fc%s?pil{)sg>;%yqHb`J?;j~F2_Np?{LB18*B~;x(RBR# zHLNw=$<dk;A0LCF@n0Cll=eAP&p_zWqcpxcZ;U7O0r^gs9Q%6rg*ENOjR<CNVx*Gf zgbysX`ueKX_8>e{2qje0+E->CW_M$C$)@qI$rc|Ic$LH7$iIKP2Jt?vst)0`d(g;y z-Q`h<tBksQXlrN(o3G0&X<ccuxr^h%naH`rE#z_kyhigG9gZ6w8Q}pnQ`Mqw2YpdA zI`}Fq?ushsAt)LGRY!k6*rRjsFq|JYM0`!J+NiX-O)&L&VDT>^XmtRuhvN37EKqX@ zJA9<I++*KtTiHPF0AuE>)%rdl{-va#!s`*mgq0lhS?gm*e8sXSnML>WZb^mGaE_C| zN%4^?B}QJ3oGT!MkAp4RJ!7<0?t7jrLPMK(okp-S6W?zGvr=-vqE)cFew^O>jsO#g zYHH{|lO#96K0gPt$JMM?exH<!6kY^Mm(^}>dpG<^JUQveaH(U-_!v`3x+%)H@V(c7 z?sz?Y-7mX!6Vyg}BTcaw-f7YyV<$1~n_!+3(%c%QW%d{+-5`w~_0Vc?&?ht@ge;xO zn2yo3@pU<OtC!V8vsJE_)Ui=A2egG0+sp*<fqaMfGf$e_#y09aS?M~@h0GkzT&p;Q zTj@a6#DKiZ>H7->39ldJ@K>GW)Ght3m#o<dlSlgpyWJ7_>?^IV{l65p0ObVrv8?kt z4YXtI{%%SL9f(QaYp({H*oBz0m0Etghzj!5wpVY^M!H})PyK+yo#6TcjU<L}^x(9l zFopI?EDgc(#*neKGxqv_t7+ZLfRICEgU?D!8ehnA$u5?&s$kAP%{@Cyf0WA-Lm<mN zAiAAZwKUHPP~f9N_5a5&vZNsp58Ri1*5>9pOz?%qHk#%*1ilIa*+f1C0ZgSb6#+e1 zSu$3I^CzdgB|0Xj8S<E<48G$#2K_`Lj*Fn<Ir?RTO{zTI)?6d39nYLBr!rzkYokmk zF7>=tSSzjD$uwv%|M8g{%^Jz*f}vqu7j`$FH9Fvl!Ad;nE9sfa<kP!4Bh%z7k0G=w z@@F^y{bi86WNS}S{nqC_gQ`bWrPkW(S9swau_*`Lq-B1`l@ynz9Y$c2YR1O!>10;S z#1!G6J>#^W8}7HX%DHMALG`u4VmrgQGrnr!YchYmsvdM6qdk?^<Xs3Z?=E?Ed6^mU zJrYIQB?+HVRe`djtuoD{-O?PZ!eBJKny}xulfb1vmENP2QQmiCmHx>7=hF_vThH}n zN)#O4u-mzvLTk6*O(gNUZ7VtDx6hs70F53ww<UFB9HSf5ZsJpvp@-#iN!9o|thu@N zbpVdhb3af@LA!MNSF>>@oPmEYZ7@m`E$lGuz6W<any})TgomStPjGXj*!lxTi9^I` z>BK1MZEpM;k#aG5=*dUp%x_i<9wVqIO|zhB(5hjKSgq4G(kEC7*)e*w_}xwu$a8us zQG@42c(3PbC-!CVxe;>uYB3XTzdmhU)Y!QVfskI1dm*X8D31HoN<<Rij&RxdOv31m z8O|+oU$>vK>i)UYC+c(}o|BfR(dxNm%;<MiL&aSJyU`XQvXZ1La8>*%46zlz;IbrE z;WhR7Z3LbogWsseoNjgP&*xoXL`tnq7gE_6Ls)$Uez^zN{_eDX)!FSdl-CH}I_@&W z`(Rq9M#*hO8M^g)I5#S7Rt|@qqaO)koZJoO?tw~2+$ltak|cEc`7Z<=5bO6o`PJys z#%r<uv~H_-63>#lV0_A?T$Z3pG{1Udo|9s}{ySqj3q|ui)=3T*-ni2*$_av9_i`3+ zc^xshz|Zm+A6=f~oNB)*-!IlV4Rh_4+dFl&gkzsNtB`JL7KA>BKD<Nj#=>IN@)IJ6 zJV??*-dv$>#^x80tk=HYQgfR@F4WUubx?j?OQk2jwI%Q|C1`kIz4l&cy0$oBy^CaB zVqgE%sUvVH<2WkM$j7<W)Cw1Xmp;Ivt-hB_YWw~<zqctfb8^@->ep0$tf?VV7}`RS zDIlZSTsXZWqvE!goX6n1p<Q;8=%Qp)x!xnu>-z!*RHK`IW-`ZR9UFv`KhN`&wT7|T zqgp2?kvOHtwr%jz`jc(aUY&Z=o{iH3lUZ~PazosHamdkk>qDOb;q6P<VtXw?&i83> zp=xUF#>q)M<H^@}Mxezn+KIdew9yQ;%7a#Ahrflz5nI&-IOBssD00KvDdTvy2VS^% zl)IGaH#K^?F>gizLl2h<73P;VZzvB^q#SM(@o3ZUfdEicppY?ep^#d9;qY72K|E(v zhM1zSjwT0dcX`S%3X3p^|C6<<Z9{u^4c^&B_hGuIN?+-yZnq%*w!}TS;oWA0s(UpZ z@8Qaz^UiqXya2ync^V@4a4>1$t6OEAB17FvhB^Z+$HW6X@Ok*#cJPruC5S?|xk7hJ zw|YLpjjlGo-XXJ}S(=Q_XC+u^rqZqbU1GvxUhnpKd=%8@_>G@0g^2Z^x3O)Fgf?3% zTsyD0_bFaF&oieYC)k)UdvM47XWY*8K`Hx<gFP9K8Fm@PjPkP8S4z;=Uv=~7cfMDJ zrEL@{`Pt5L_Tm>|(1a2l?;$9K)&bV<_tuT547cueo5CFLmct6H1pZJa!>eL*V~F%> zx{xk$ap;LcFBG<rvS$Zgp`4+H-OCbC*2q+i|FUY)rB%v3X&C7GHAX;VT*PPjqrl=F z5kTUsj6D=##wW*}3q>P_POaXW&|2l^jfEal`HNzHW8no@k(Kw75%^STGF8Sx<C&m& zcEyjh+k+HoANBs^HQKp1H~BI#ySlo&75tg3ELJw&Cw#RkOXF^jOtcy8YL)m9>kzw^ z8J!ThX@@ana&$;E_Idi?YWzyhJYt%&B)yFkgv2`)W$Oa6rJ_b!4zX^~-1~*Xr&R}C zhxb78@&xO>&>_5#OMV{mRPL+^>f=`pn)SG_-shUz1rv_nK3gu$Mz=-~I7zN9FV#LD zRG0dQFxK=(H&P~W8H>RQH;S|z?QPHt_BPmM`}{Jf>4;>ZFm9q!ShJ>BuNAx|*0pI1 z^=eQ{^I9Rl4x@pfb}*UkP_4bgG$FBB3~7FpS~$Mu=wwP=h)8H7fLIjRUr4>!>uoxl zZ9yj_Y*khFc`3jrz1iVg!Z|#y@VfBsG0=Z7xc?mb)dmLY;ZWMf22_8}=x9uUx9BTj zqRg+jiN_T7t4xXEi;hrDXAb8=6N_`t^X*KE4t4FtPSu%##mSXc=EgD>Wd<JKo|t{Q z`DvPT3R~Z>a2mFif~GAKY+p9Ls8;cbaA}_4We8;Dto4P%?Au}qLEGxN)j(mB>AY|5 z_p(+|e-dNk$_7deLEN`g?8&kxmXPQoU36W$Ch!!OR||*3!-fj9Y)f~29V+U5ZD)(B ztX}THwZ!H{+8I(djgZ`<g!z4NP%Nk?HX*)mw$>zf#~U9volebdT7?`x1aF@uDqL^a z{gy|;$S><=+6@1O1wBN}a0+paq5UuH<c&h9f=#y#Z+MCN)eFO>g>1k7`nftPoZXz} ztAd$1nC3QX;;iL~cI=V!@I0J)mZyDNNt<n~^n00ym90GCx*!B{mIPNhS2SYp<F-<0 zW~r=wph?q^ldD*xmY(O1tQK^f>VITv-I<lvERn`t@kO=KK+~x3k2{Ecg<1v`UX6tH zm|%v6-#=I!OQ*iL<1JZbEnW_mCmQ=Qd+O}5r_^pdCcJ;HF{nw5wB3--3rn3Ipt8G~ zF77!w_JGRBN;}c^L=-I}Os+1F<7aox%XjeSy)Lcd%dnS80|rauYaX0nk0qEgSsflS zTcsiP<v#R&6d!0-a!<)SWO(@Kpky(^C`z}+3J#Z(lO~TwDvdArIvumjQFn&IDq*$W zh}IMX&nGrt0Is_Z66;Sz+&3^fC&%(jGbb{cC`=pF@{Ui-tKgTa%Jfe>8w$A{@l#@j z6O+{z=st&L_~yrRRi72*MYcDy{P_h1!77haTZdYH;vm&7?MSC8w4G`e+YKkIXAM3m z`m?hHpA>e;{2cR*1WMQUWN%qYL2FXv;1$&;7&^>I-)Xe*^b$GL<M*4kb9kelr%q+U zqYlSM8wWpJVX^1EEjl_e_F94P!Lrl(qNx&Eij+qVU;T0%kN2h&63V8hrnmn5v4~ny z*7L)r<zyHgu1+2)7dBPc+MUkeqiQ#7m6Ha*L0jd#7X%`V2xU{bwQhej7`7v+GKs@w z7bld@66MkUx4N)<1&9OQcuoSS4QS*k3K``vMB2nfo!mQsXBZpsCQh@sv6S+Ej`(nH z&lw+EhwDMjBQmcv|NbM<8CtS^B6@Uya>={F$S)7V_GM%65}3H>nx+z8Rw$BR_OzVC z2a(x6IW4{smE;z^qk*AdH}t~*I0xTQcF+!-`GjxxBXsWA3M9udA8e>2wzse+tv!h7 zq^`SC6Y%c$Vd5UEJ<UgUa5yrn2{S36^kw7V;IL2V(NMwh$@py}=t1}#J|dQN9hc16 z+lV8yGkTf6`usq^_Iq^5TzgQEX3xB`fzLknbo=)Vqw&~u|5nUFX7=<md8bwp7r&i6 zPi@3f!hU4$+||tKaT-@C3C|Ur=8<og<5J;BY3R{Ol{5L6e_r_M;SZ}Be9PQHH=;79 zN`a~)FXOe&*Epr*FJ*=Ejwi!Yh3dY)=AB$3p}YNg#`0I$orA#JSL>Y>*LmqRoV?NJ zk0!+`&BMsnSgYE8&WLlcGMY4e=3(C9jp@R4;<Jh;XzMVKbgS<@wiAwcOTLYw=p0?_ z(md!2xVN3j+7;|Tk&!MgZy6a=Akj_k_vb|&pC+)~Hz}W(m>`Di^B}pWkgmvD3i=5) z$n+Hu>hVBNC&5k<6_?>sN_5iQe&O^&&wsjF<BokvAj|k=5WL=W9`=yd@nE)aOG-2N zAp;Rh1B*gq(03PjMtd~6rQn?-Q*{znd!?71@igEZDEq4Y@!Wdg*=Z0LQTFto9-%XW zq(KZe68JpT1f`I(fBb*%i^EfJTc@WcAD?A`e_TAn!$gpou)FIt<PCghw=5^6^rG~c H$-DmxSAY{` From af454091f9d5fb87ba73741eebaed5c92dc1732b Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Sat, 8 Aug 2026 08:26:02 +0000 Subject: [PATCH 12/14] =?UTF-8?q?feat(qa):=20checklist-run=20=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=20skill=20+=20=E9=80=89=E6=8B=A9=E5=99=A8=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The execution counterpart to coverage-sweep (which authors items). Lets the maintainer test a specified feature point or feature FILE on demand: - scripts/checklist-select.mjs — deterministic selector resolver. Turns an item id / area: / capability: / priority: / surface: / since:vN / **file:<path>** into the concrete runnable item set. `file:` maps a framework source file to the items whose `source[]` cites it ("test whatever covers this file"), with narrowest-useful precedence (exact/basename before dir-level). Blocked items excluded by default. Carries a --self-test (12 cases) now wired into the check:platform-checklist gate, so the resolver stays healthy under the same CI. - .claude/skills/checklist-run/SKILL.md — the runner skill (internal, marked internal:true per template-consistency.test.ts). Trigger + selection + isolation/parallelism contract; defers the judging protocol to RUNNER.md and the boot know-how to dogfood-verification. Plans the run by surface (build the vendored console only for browser/mixed items), isolates per item (own port + file DB), dispatches opus runner subagents few-at-a-time for browser items, and keeps results OUT of the repo per the runs/ policy. README documents the two-skill split (coverage-sweep authors, checklist-run executes) and the selector grammar. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YD9f6FYyMraUWYeJf53V43 --- .claude/skills/checklist-run/SKILL.md | 114 ++++++++++++++++++ docs/qa/platform-checklist/README.md | 9 ++ package.json | 2 +- scripts/checklist-select.mjs | 166 ++++++++++++++++++++++++++ 4 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 .claude/skills/checklist-run/SKILL.md create mode 100644 scripts/checklist-select.mjs diff --git a/.claude/skills/checklist-run/SKILL.md b/.claude/skills/checklist-run/SKILL.md new file mode 100644 index 0000000000..73ecaa0e41 --- /dev/null +++ b/.claude/skills/checklist-run/SKILL.md @@ -0,0 +1,114 @@ +--- +name: checklist-run +description: > + Execute the platform test checklist (docs/qa/platform-checklist/) against a real + running app and produce a run record. Use whenever the maintainer says "测一下 + <功能>", "跑这个测试项", "run the checklist for <area/item>", "test this feature + file", "验证 <功能点>", or points at a framework source file and asks whether it + still works. Takes a SELECTOR (item id · area · capability kind · priority · a + release · or a source-file path) and drives every matched item through its steps + following RUNNER.md. The companion to `coverage-sweep` (which AUTHORS items); this + one RUNS them. NOT a customer-published skill — internal agent tooling (lives in + .claude/, never in the published `skills/` dir). +metadata: + # Hides this skill from interactive `npx skills add objectstack-ai/objectstack` + # discovery — every SKILL.md outside `skills/` must carry this marker + # (template-consistency.test.ts enforces it). + internal: true +--- + +# Checklist run — execute selected items against a live app + +You resolve a **selector** to a set of checklist items, boot the app in isolation, drive +each item's steps in the browser / over the API, and emit a **run record**. The method +for judging each clause (verdicts, oracle hierarchy, evidence, the anti-false-positive +self-check) is **`docs/qa/platform-checklist/RUNNER.md`** — read it first and obey it; +this skill is the trigger, the selection contract, and the isolation/parallelism plan, +not a second copy of the runner protocol. + +Environment know-how (boot, the dist build model, the vendored-console trap, browser +escape hatches) is the **`dogfood-verification`** skill — read it too. You are not +reinventing how to boot; you are executing a specific list against a boot. + +## 0. Resolve the selector — deterministic, no guessing + +Never eyeball which items to run. Ask the resolver: + +``` +node scripts/checklist-select.mjs <selector> --json +``` + +Selectors (one per run): + +| selector | runs | +|---|---| +| `platform-core.console-login` (bare id) | that one item | +| `area:records-forms` (or bare `records-forms`) | every item in the area | +| `capability:hook` | items mapped to a metadata kind in `coverage.json` | +| `priority:P0` | the standing smoke | +| `surface:api` | every API-surface item (cheap — no browser build needed) | +| `since:v17` | everything introduced in a release (the release-sweep filter) | +| **`file:packages/plugins/plugin-approvals/src/approval-service.ts`** | **items whose `source[]` cites that file — "test whatever covers this file"** | +| `all` | the whole checklist | + +`--json` gives the runnable list (id · priority · surface · revision). **Blocked items are +excluded by default** — they can't run on stock fixtures; pass `--include-blocked` only to +record them as `blocked` with their fixture reason. **Pin the `revision`** the resolver +reports into the run record: a verdict is only valid for the revision it ran against. + +## 1. Plan the run by surface — build only what you need + +Read the matched items' `surface`: + +- **All `api` / `build` / `cli`** → no console build. Boot the framework (`objectstack dev`) + and drive REST/CLI. Fast (~minutes). +- **Any `browser` / `mixed`** → you need the vendored console dist. It builds SEPARATELY + from the showcase workspace closure (`pnpm objectui:build` from the pinned `.objectui-sha`); + the first boot 404s `/_console/` until it exists (dogfood §2 — a real precondition, record + it, don't fake a block). Budget the build (~10–30 min on a cold monorepo); it dominates + wall time, the browser driving is minutes. + +Build once, up front, for the whole run. + +## 2. Isolate, then execute (per dogfood §0) + +- Own free non-default port + own file DB **per concurrently-running item** + (`--seed-admin -d file:/tmp/<run>/<item>.db`). Two runs sharing a port/DB/browser tab is + the `shared-browser-tab` trap. +- **Parallelism:** fan API-surface items out in parallel (each its own port, cheap). Run + browser items **few-at-a-time** (2–3), each its own port + browser context — a single + machine's CPU and one shared display contend past that. When dispatching runner + subagents, **they must be `opus`**, each given: the item JSON, RUNNER.md, the + dogfood skill, its own port/DB, and the results-out-of-repo rule (§4). +- Execute each item's `steps` faithfully; judge each `acceptance` clause and each + `negative` against its declared `oracle`, capturing the `evidence` the clause names. + **Server truth outranks pixels; DOM only after a screenshot confirms render; a `fail` + needs reproduction ×2 + the automation self-check + a filed issue** (RUNNER §rules). + +## 3. When the run teaches you something about the ITEM + +A run that discovers the item's `steps` are wrong (a moved route, a renamed key, an +expiry path that needs localStorage cleared too) is the checklist working. That is a +checklist EDIT — do it in a **worktree** (PD#11): revise the item, bump `revision`, append +a `history` entry, keep `node scripts/check-platform-checklist.mjs` green, and land it on a +task branch. Product defects found while running go to `FOLLOW-UPS.md` (or a filed issue) +as expected-fail probes — never tick a clause green over a real defect. + +## 4. The run record — results do NOT go in the repo + +Write one JSON per run in the shape RUNNER.md defines (env with framework sha + +`.objectui-sha` + port + db; per-clause verdicts each naming its evidence; derived item +verdict; issues). **`runs/` is git-ignored** — the record and its screenshots stay in the +executing environment / the tracking issue / an external QA store, never committed. The +committed source is the checklist under `areas/`; a run is a dated assertion about one +build and belongs with that build's artifacts. Report the per-clause verdict table + the +evidence paths + the env-setup-vs-test time split back to the maintainer. + +## Guardrails + +- **Don't fake coverage.** Missing fixture → `blocked(fixture)` with the reason; unbuilt + console → build it or record `blocked(environment)`; a half-proven item is `partial`, + not `pass`. A blocked verdict WITH evidence is a successful run; a faked pass is not. +- **Don't run blocked items as if runnable** — the resolver hides them for this reason. +- **One selector, one run record.** For a release sweep, run `since:vN` and `priority:P0` + as separate records rather than smearing them together. diff --git a/docs/qa/platform-checklist/README.md b/docs/qa/platform-checklist/README.md index ee1e7002f3..0a22a00bc2 100644 --- a/docs/qa/platform-checklist/README.md +++ b/docs/qa/platform-checklist/README.md @@ -12,6 +12,15 @@ Validated in CI: `pnpm check:platform-checklist` (`scripts/check-platform-checklist.mjs`). Execution protocol for agents: [RUNNER.md](./RUNNER.md). Run records: [runs/](./runs/README.md). +**Two internal skills drive this ledger** (`.claude/skills/`, never published): +`coverage-sweep` **authors** items (find gaps → write them, per +[SWEEP.md](./SWEEP.md)); `checklist-run` **executes** them (pick items by selector → +drive them → emit a run record, per [RUNNER.md](./RUNNER.md)). The runner resolves what +to test with `scripts/checklist-select.mjs <selector>` — an item id, an `area:`, a +`capability:`, a `priority:`, a `since:vN` release, or a **`file:<path>`** that maps a +framework source file to the items whose `source` cites it ("test whatever covers this +file"). + ## Layout ``` diff --git a/package.json b/package.json index 2ded22ef57..462d6d1894 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "check:skill-frame-freshness": "node scripts/check-skill-frame-freshness.mjs --self-test && node scripts/check-skill-frame-freshness.mjs", "check:skill-compatibility": "node scripts/check-skill-compatibility-version.mjs --self-test && node scripts/check-skill-compatibility-version.mjs", "check:adr-anchors": "node scripts/check-adr-anchors.mjs --self-test && node scripts/check-adr-anchors.mjs", - "check:platform-checklist": "node scripts/check-platform-checklist.mjs", + "check:platform-checklist": "node scripts/checklist-select.mjs --self-test && node scripts/check-platform-checklist.mjs", "check:org-identifier": "node scripts/check-org-identifier.mjs", "check:authz-resolver": "node scripts/check-single-authz-resolver.mjs --self-test && node scripts/check-single-authz-resolver.mjs", "check:slot-lookup": "node scripts/check-slot-lookup-ratchet.mjs", diff --git a/scripts/checklist-select.mjs b/scripts/checklist-select.mjs new file mode 100644 index 0000000000..11d702a984 --- /dev/null +++ b/scripts/checklist-select.mjs @@ -0,0 +1,166 @@ +#!/usr/bin/env node +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. +// +// checklist-select — resolve a SELECTOR into the concrete set of platform-checklist +// items to run. The deterministic front half of the `checklist-run` skill: the skill +// drives a browser, this script decides WHAT to drive, with zero LLM guesswork. +// +// node scripts/checklist-select.mjs <selector> [--json] [--include-blocked] +// node scripts/checklist-select.mjs --self-test +// +// ## Selectors (one per invocation) +// +// <area>.<slug> an exact item id e.g. platform-core.console-login +// item:<id> same, explicit +// area:<area> every item in an area e.g. area:records-forms +// <area> bare area name = area:<area> e.g. approvals +// capability:<kind> items mapped to a metadata kind in coverage.json e.g. capability:hook +// priority:P0|P1|P2 every item at that priority +// surface:browser|api|... every item on that execution surface +// since:vN every item introduced in release vN (prefix match: since:v16 ⊇ v16.0) +// file:<path> ★ items whose `source[]` cites this framework file (or its +// basename / containing dir) — "test whatever covers this file" +// all every active item +// +// Blocked items (carrying `blocked:{by,ref}`) are EXCLUDED by default — they cannot run +// on stock fixtures. Pass --include-blocked to list them too (the runner records them as +// blocked with their fixture reason, per RUNNER.md). +// +// Output: a table (id · priority · surface · blocked?) to stderr for humans, and — with +// --json — a machine list to stdout for the runner to fan out over. + +import { readdirSync, readFileSync, existsSync } from 'node:fs'; +import { join, basename, dirname } from 'node:path'; + +const ROOT = new URL('..', import.meta.url).pathname; +const AREAS_DIR = join(ROOT, 'docs/qa/platform-checklist/areas'); +const COVERAGE = join(ROOT, 'docs/qa/platform-checklist/coverage.json'); + +/** Load every item once, tagged with its area. */ +function loadItems(areasDir = AREAS_DIR) { + const items = []; + for (const f of readdirSync(areasDir).filter((f) => f.endsWith('.json')).sort()) { + const doc = JSON.parse(readFileSync(join(areasDir, f), 'utf8')); + for (const it of doc.items || []) items.push(it); + } + return items; +} + +/** + * Resolve a selector string against a set of items (+ optional coverage map). + * Pure and side-effect-free so the self-test can exercise it directly. + * @returns {object[]} the matched items (order: as declared) + */ +export function selectItems(selector, items, coverage = { metadataKinds: {} }) { + const active = items.filter((it) => it.status === 'active'); + const byId = (id) => active.filter((it) => it.id === id); + + if (selector === 'all') return active; + + const [rawKey, ...rest] = selector.includes(':') ? selector.split(':') : [null, selector]; + const key = rawKey; // null when the selector had no prefix + const val = rest.join(':'); // rejoin so file:path/with:colons survives (rare) + + if (key === 'item') return byId(val); + if (key === 'area') return active.filter((it) => it.id.startsWith(`${val}.`)); + if (key === 'capability') { + const mapped = new Set((coverage.metadataKinds?.[val]?.items) || []); + return active.filter((it) => mapped.has(it.id)); + } + if (key === 'priority') return active.filter((it) => it.priority === val); + if (key === 'surface') return active.filter((it) => it.surface === val); + if (key === 'since') return active.filter((it) => it.since === val || (it.since || '').startsWith(`${val}.`)); + if (key === 'file') { + const p = val.replace(/^\.?\//, ''); + const base = basename(p); + const dir = dirname(p); + // Narrowest-useful precedence: prefer items citing the exact path or its + // basename ("test whatever covers THIS file"); only when nothing cites the + // file directly fall back to a directory-level match (so `file:<a-dir>` + // still resolves the items covering that area of the tree). + const exact = active.filter((it) => (it.source || []).some((s) => s.includes(p) || s.includes(base))); + if (exact.length) return exact; + if (dir !== '.') return active.filter((it) => (it.source || []).some((s) => s.includes(dir))); + return []; + } + + // No recognized prefix → treat the whole string as an id, else as an area name. + if (key === null) { + const asId = byId(val); + if (asId.length) return asId; + return active.filter((it) => it.id.startsWith(`${val}.`)); + } + return []; // unknown prefix +} + +function isBlocked(it) { + return it.blocked !== undefined; +} + +// ── self-test ──────────────────────────────────────────────────────────────── +if (process.argv.includes('--self-test')) { + const FIX = [ + { id: 'a.one', status: 'active', priority: 'P0', surface: 'browser', since: 'v16', source: ['packages/foo/bar.ts'] }, + { id: 'a.two', status: 'active', priority: 'P1', surface: 'api', since: 'v16.1', source: ['#3358'], blocked: { by: 'fixture', ref: '#1' } }, + { id: 'b.three', status: 'active', priority: 'P0', surface: 'api', since: 'v15', source: ['packages/foo/baz.ts'] }, + { id: 'b.gone', status: 'retired', priority: 'P0', surface: 'api', since: 'v15', retiredReason: 'x' }, + ]; + const COV = { metadataKinds: { hook: { items: ['a.one'] } } }; + const ids = (sel) => selectItems(sel, FIX, COV).map((i) => i.id).sort(); + const eq = (got, want, name) => { + const g = JSON.stringify(got), w = JSON.stringify(want); + if (g !== w) { console.error(`✗ ${name}: got ${g}, want ${w}`); process.exit(1); } + }; + eq(ids('all'), ['a.one', 'a.two', 'b.three'], 'all excludes retired'); + eq(ids('a.one'), ['a.one'], 'bare id'); + eq(ids('item:a.one'), ['a.one'], 'item: prefix'); + eq(ids('a'), ['a.one', 'a.two'], 'bare area'); + eq(ids('area:b'), ['b.three'], 'area: prefix'); + eq(ids('capability:hook'), ['a.one'], 'capability via coverage'); + eq(ids('priority:P0'), ['a.one', 'b.three'], 'priority'); + eq(ids('surface:api'), ['a.two', 'b.three'], 'surface'); + eq(ids('since:v16'), ['a.one', 'a.two'], 'since prefix (v16 ⊇ v16.1)'); + eq(ids('file:packages/foo/bar.ts'), ['a.one'], 'file exact'); + eq(ids('file:foo'), ['a.one', 'b.three'], 'file dir match'); + eq(ids('nope.xxx'), [], 'unknown id → empty'); + console.log('✓ checklist-select self-test: 12 cases pass.'); + process.exit(0); +} + +// ── CLI ────────────────────────────────────────────────────────────────────── +const args = process.argv.slice(2); +const json = args.includes('--json'); +const includeBlocked = args.includes('--include-blocked'); +const selector = args.find((a) => !a.startsWith('--')); + +if (!selector) { + console.error('usage: node scripts/checklist-select.mjs <selector> [--json] [--include-blocked]'); + console.error(' selectors: <id> | area:<a> | capability:<k> | priority:P0 | surface:api | since:vN | file:<path> | all'); + process.exit(2); +} +if (!existsSync(AREAS_DIR)) { + console.error(`checklist-select: ${AREAS_DIR} not found`); + process.exit(1); +} + +const items = loadItems(); +const coverage = existsSync(COVERAGE) ? JSON.parse(readFileSync(COVERAGE, 'utf8')) : { metadataKinds: {} }; +let matched = selectItems(selector, items, coverage); +const droppedBlocked = includeBlocked ? [] : matched.filter(isBlocked); +if (!includeBlocked) matched = matched.filter((it) => !isBlocked(it)); + +if (json) { + process.stdout.write(JSON.stringify(matched.map((it) => ({ id: it.id, priority: it.priority, surface: it.surface, since: it.since, revision: it.revision })), null, 2) + '\n'); +} + +console.error(`\nselector: ${selector} → ${matched.length} runnable item(s)${droppedBlocked.length ? ` (${droppedBlocked.length} blocked, hidden — pass --include-blocked)` : ''}\n`); +for (const it of matched) { + console.error(` ${it.priority} ${String(it.surface).padEnd(8)} ${it.id}${isBlocked(it) ? ' [BLOCKED]' : ''}`); +} +if (droppedBlocked.length) { + console.error(`\n hidden (blocked): ${droppedBlocked.map((i) => i.id).join(', ')}`); +} +if (matched.length === 0) { + console.error(' (nothing matched — check the selector; try `all` or `area:<name>`)'); + process.exit(1); +} From d167ff909481d14d317aa3959cc52c6b0f144fcb Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Sat, 8 Aug 2026 12:03:59 +0000 Subject: [PATCH 13/14] =?UTF-8?q?chore(qa):=20=E5=B9=B3=E5=8F=B0=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=B8=85=E5=8D=95=E6=A0=A1=E9=AA=8C=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E5=AE=9A=E6=9C=9F=E6=89=8B=E5=8A=A8,=E4=BB=8E=20CI=20=E7=A7=BB?= =?UTF-8?q?=E9=99=A4(=E7=BB=B4=E6=8A=A4=E8=80=85=E5=86=B3=E5=AE=9A)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By maintainer decision the platform test checklist is a QA ledger, not a per-PR code gate. Removed the `check:platform-checklist` step from lint.yml so no unrelated PR is ever blocked by checklist drift; the script stays for manual / periodic use (`pnpm check:platform-checklist`), run before a release or after a large platform surface lands. README + the lint.yml note updated to say so. Trade-off understood and accepted: a NEW metadata kind landing on main (like `capability` did) will no longer fail a PR — it surfaces at the next manual run instead. The coverage ratchet still exists; it just isn't a blocking gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YD9f6FYyMraUWYeJf53V43 --- .github/workflows/lint.yml | 18 +++++++----------- docs/qa/platform-checklist/README.md | 9 ++++++--- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8e04d25e73..59e3d0b825 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -193,17 +193,13 @@ jobs: - name: ADR anchors + number uniqueness (governed code names its decision) run: pnpm check:adr-anchors - # Standing platform test checklist (docs/qa/platform-checklist/): the - # AI-executed successor to the per-release checkbox issues (#3358) and - # hand tables (release-15.1-test-plan.md). Structural gate only: ids - # immutable+unique, revision matches history (a semantic edit that forgot - # to bump would silently re-validate old run verdicts), every acceptance - # clause names its oracle (an oracle-free clause is a tick-on-vibes - # invitation). The invariants travel in the script's header and failure - # output; authoring/execution contracts live next to the data (README.md / - # RUNNER.md). - - name: Platform test checklist ledger - run: pnpm check:platform-checklist + # NOTE: the standing platform test checklist (docs/qa/platform-checklist/) + # is validated by `pnpm check:platform-checklist`, but by MAINTAINER + # DECISION it is NOT wired into CI — it runs on a periodic manual cadence + # (before a release / after a large platform surface lands), not on every + # PR. The checklist is a QA ledger, not a code gate; keeping it out of the + # per-PR path means an unrelated PR is never blocked by checklist drift. + # Run it by hand: `pnpm check:platform-checklist`. See that dir's README. # #3280/#3290 org-identifier guard: `organizationId` is the blessed # developer-facing name for the caller's active org in hook/action bodies; diff --git a/docs/qa/platform-checklist/README.md b/docs/qa/platform-checklist/README.md index 0a22a00bc2..44bbaea507 100644 --- a/docs/qa/platform-checklist/README.md +++ b/docs/qa/platform-checklist/README.md @@ -8,9 +8,12 @@ checkbox issue per release (#3358) — with one ledger that **accumulates across releases**, supports append/change without losing history, and pins every tick to an acceptance oracle and captured evidence. -Validated in CI: `pnpm check:platform-checklist` -(`scripts/check-platform-checklist.mjs`). Execution protocol for agents: -[RUNNER.md](./RUNNER.md). Run records: [runs/](./runs/README.md). +Validated by `pnpm check:platform-checklist` (`scripts/check-platform-checklist.mjs`) — +a zero-dependency structural + coverage check. **By maintainer decision it runs on a +periodic MANUAL cadence, not in CI**: run it before a release, after a large platform +surface lands, or alongside a `coverage-sweep` / `checklist-run`. It is a QA ledger, not +a per-PR code gate, so an unrelated PR is never blocked by checklist drift. Execution +protocol for agents: [RUNNER.md](./RUNNER.md). Run records: [runs/](./runs/README.md). **Two internal skills drive this ledger** (`.claude/skills/`, never published): `coverage-sweep` **authors** items (find gaps → write them, per From 3af66ea743b3177f23461b70d3b9633018939978 Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Sat, 8 Aug 2026 13:08:06 +0000 Subject: [PATCH 14/14] =?UTF-8?q?docs(qa):=20README=20=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E3=80=8C=E8=BF=90=E8=A1=8C=E8=8A=82=E5=A5=8F=E3=80=8D=E5=B9=B6?= =?UTF-8?q?=E5=85=A8=E9=9D=A2=E6=A0=A1=E5=87=86=E5=8E=BB-CI=20=E5=90=8E?= =?UTF-8?q?=E7=9A=84=E6=8E=AA=E8=BE=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added an 'Operating cadence — when to run this (it is NOT in CI)' section to the checklist README: run `pnpm check:platform-checklist` before a release, after a large platform surface lands, when touching the checklist, or beside a coverage-sweep/checklist-run — with the trade-off (a new kind/enum that lands between runs is caught at the next manual run, not on merge) spelled out and the one-line re-add if latency ever matters more than PR independence. Corrected every now-stale "fails CI" reference across README, coverage.json $comment, and 4 area-file history entries to say the manual check flags/​catches the drift instead of blocking a PR. Validator green (178 items, 27/2). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YD9f6FYyMraUWYeJf53V43 --- docs/qa/platform-checklist/README.md | 70 ++++++++++++------- .../platform-checklist/areas/automation.json | 2 +- .../platform-checklist/areas/dashboards.json | 2 +- .../areas/integration-system.json | 2 +- .../areas/records-forms.json | 4 +- docs/qa/platform-checklist/coverage.json | 2 +- 6 files changed, 52 insertions(+), 30 deletions(-) diff --git a/docs/qa/platform-checklist/README.md b/docs/qa/platform-checklist/README.md index 44bbaea507..e281bd0c15 100644 --- a/docs/qa/platform-checklist/README.md +++ b/docs/qa/platform-checklist/README.md @@ -112,11 +112,13 @@ Design notes: `coverage.json` makes "凡是有的能力, 都要测试" mechanical instead of aspirational. The universe of capabilities is **derived, not hand-kept**: every metadata kind with a `packages/spec/liveness/<kind>.json` ledger (the ADR-0049 governed set) must be mapped -to at least one checklist item, or waived with a written reason. The validator enforces -both directions — an unmapped kind fails CI (the platform grew a capability the -checklist doesn't test), and a mapped kind whose liveness ledger disappeared fails too -(the entry outlived the capability). This is the `examples/app-showcase/src/coverage.ts` -demonstrated-or-waived ratchet, applied to testing instead of demonstration. +to at least one checklist item, or waived with a written reason. The validator +(`pnpm check:platform-checklist`, run on the manual cadence below — **not** wired into +CI) reports both directions — an unmapped kind is flagged (the platform grew a +capability the checklist doesn't test), and a mapped kind whose liveness ledger +disappeared is flagged too (the entry outlived the capability). This is the +`examples/app-showcase/src/coverage.ts` demonstrated-or-waived ratchet, applied to +testing instead of demonstration. Enumerable surfaces *inside* a capability (49 field types, 20 chart types, flow node types, query operators, decision actions, …) are covered by `variants` matrices on the @@ -135,20 +137,20 @@ authored against with an `enumSource` field: The validator extracts the enum's *current* member count from that source (comment- stripped, deduped) and fails when it no longer equals `expect`. So when the platform -grows a 50th field type or a 21st chart type, this item's next CI run goes red with a -precise instruction: revise the variants matrix, bump the item revision, set `expect` -to the new count. This closes the automation gap the coverage ratchet alone left — the +grows a 50th field type or a 21st chart type, the next `check:platform-checklist` run +goes red with a precise instruction: revise the variants matrix, bump the item revision, +set `expect` to the new count. This closes the gap the coverage ratchet alone left — the kind-level ratchet catches a *new metadata kind*, `enumSource` catches a *new value in -an existing kind's enum* — so "spec grew a variant" becomes a checklist-blocking event -instead of a silent drift the showcase `coverage.test.ts` only catches indirectly. -Items currently pinned: field types, chart types, action locations, webhook triggers, -flow node types. Pin more as matrices are added. +an existing kind's enum* — so "spec grew a variant" is caught by the manual check +instead of drifting silently (the showcase `coverage.test.ts` only catches it +indirectly). Items currently pinned: field types, chart types, action locations, webhook +triggers, flow node types. Pin more as matrices are added. A waiver is a debt marker, not an exemption: it names what fixture or surface is missing, so paying it down is a matter of adding the fixture and flipping the entry to `items`. -### Variants freshness — spec enum drift fails CI on the item itself +### Variants freshness — spec enum drift is caught on the item itself Matrix items may pin the spec enum their `variants` were authored against: @@ -158,19 +160,39 @@ Matrix items may pin the spec enum their `variants` were authored against: The validator extracts the enum's CURRENT member count from the spec source at check time (comment-stripped, deduped) and fails with `VARIANTS STALE` when it no longer -equals `expect` — so a PR that adds a 50th field type cannot merge without revising the -matrix (or consciously bumping `expect` with a revision). This closes the loop the -kind-level ratchet leaves open: new *kinds* are caught by the liveness-derived universe, -new *members of an existing kind* are caught by these pins. Enums declared inline -(anonymous `z.enum` inside an object literal) cannot be pinned by export name — those -matrices still rely on the showcase `coverage.test.ts` demonstrability gate. - -### How the checklist keeps itself current (the automation model) +equals `expect` — so the next `check:platform-checklist` run after a 50th field type +lands flags the matrix as stale (revise it, or consciously bump `expect` with a +revision). This closes the loop the kind-level ratchet leaves open: new *kinds* are +caught by the liveness-derived universe, new *members of an existing kind* by these pins. +Enums declared inline (anonymous `z.enum` inside an object literal) cannot be pinned by +export name — those matrices still rely on the showcase `coverage.test.ts` +demonstrability gate. + +### Operating cadence — when to run this (it is NOT in CI) + +By maintainer decision `check:platform-checklist` is **not** wired into per-PR CI: the +checklist is a QA ledger, not a code gate, so an unrelated PR is never blocked by +checklist drift. It runs on a **manual / periodic cadence** instead. Run +`pnpm check:platform-checklist` (zero-dependency, ~1s, no tokens): + +- **before a release** — part of the release sweep below; +- **after a large platform surface lands** — a new metadata kind, a new enum, a new area; +- **whenever you touch the checklist** — the structural + coverage check catches a + dangling id or a forgotten `revision` bump in your own edit; +- **alongside a `coverage-sweep`** (find gaps) **or `checklist-run`** (execute items). + +The trade-off of staying out of CI: a new capability kind or enum value that lands on +`main` between runs is caught at the **next** manual run, not the moment it merged. The +ratchets still detect it — they just aren't a blocking gate. If drift-catching latency +ever matters more than PR independence, re-adding the one-line CI step +(`run: pnpm check:platform-checklist`) restores the automatic posture. + +### How the checklist keeps itself current 1. **New capability kind** → a `packages/spec/liveness/<kind>.json` ledger appears → - coverage ratchet fails CI until the kind is mapped or waived. Automatic. -2. **New member of an enumerable surface** → `enumSource` pin fails CI on the matrix - item. Automatic for pinned enums. + the coverage ratchet flags it the next time `check:platform-checklist` runs. +2. **New member of an enumerable surface** → the `enumSource` pin flags the matrix as + stale the next time the check runs (for pinned enums). 3. **New feature inside an existing kind** → process: the feature PR lands a `since: v<current>` item (same discipline as changesets); the release sweep filter catches stragglers. diff --git a/docs/qa/platform-checklist/areas/automation.json b/docs/qa/platform-checklist/areas/automation.json index fb99c4de36..35308fad62 100644 --- a/docs/qa/platform-checklist/areas/automation.json +++ b/docs/qa/platform-checklist/areas/automation.json @@ -387,7 +387,7 @@ { "revision": 2, "date": "2026-08-08", - "change": "pinned enumSource for the variants-freshness ratchet — spec enum drift now fails CI on this item directly", + "change": "pinned enumSource for the variants-freshness ratchet — spec enum drift is caught by the manual check on this item directly", "ref": "claude/platform-test-checklist-ocwugl" } ], diff --git a/docs/qa/platform-checklist/areas/dashboards.json b/docs/qa/platform-checklist/areas/dashboards.json index f087552a36..0ab6bf2173 100644 --- a/docs/qa/platform-checklist/areas/dashboards.json +++ b/docs/qa/platform-checklist/areas/dashboards.json @@ -421,7 +421,7 @@ { "revision": 3, "date": "2026-08-08", - "change": "pinned enumSource for the variants-freshness ratchet — spec enum drift now fails CI on this item directly", + "change": "pinned enumSource for the variants-freshness ratchet — spec enum drift is caught by the manual check on this item directly", "ref": "claude/platform-test-checklist-ocwugl" } ], diff --git a/docs/qa/platform-checklist/areas/integration-system.json b/docs/qa/platform-checklist/areas/integration-system.json index 4636bef600..83bc2b0665 100644 --- a/docs/qa/platform-checklist/areas/integration-system.json +++ b/docs/qa/platform-checklist/areas/integration-system.json @@ -604,7 +604,7 @@ { "revision": 3, "date": "2026-08-08", - "change": "pinned enumSource for the variants-freshness ratchet — spec enum drift now fails CI on this item directly", + "change": "pinned enumSource for the variants-freshness ratchet — spec enum drift is caught by the manual check on this item directly", "ref": "claude/platform-test-checklist-ocwugl" } ], diff --git a/docs/qa/platform-checklist/areas/records-forms.json b/docs/qa/platform-checklist/areas/records-forms.json index 9418e1cc89..4b42df13a1 100644 --- a/docs/qa/platform-checklist/areas/records-forms.json +++ b/docs/qa/platform-checklist/areas/records-forms.json @@ -280,7 +280,7 @@ { "revision": 2, "date": "2026-08-08", - "change": "pinned enumSource for the variants-freshness ratchet — spec enum drift now fails CI on this item directly", + "change": "pinned enumSource for the variants-freshness ratchet — spec enum drift is caught by the manual check on this item directly", "ref": "claude/platform-test-checklist-ocwugl" } ], @@ -1456,7 +1456,7 @@ { "revision": 2, "date": "2026-08-08", - "change": "pinned enumSource for the variants-freshness ratchet — spec enum drift now fails CI on this item directly", + "change": "pinned enumSource for the variants-freshness ratchet — spec enum drift is caught by the manual check on this item directly", "ref": "claude/platform-test-checklist-ocwugl" } ], diff --git a/docs/qa/platform-checklist/coverage.json b/docs/qa/platform-checklist/coverage.json index da1a26c50c..f039596adb 100644 --- a/docs/qa/platform-checklist/coverage.json +++ b/docs/qa/platform-checklist/coverage.json @@ -1,5 +1,5 @@ { - "$comment": "Capability-coverage ratchet for the platform test checklist. The universe of governed metadata kinds is DERIVED at check time from packages/spec/liveness/*.json (the ADR-0049 ledger set) — this file must map EVERY kind to at least one checklist item, or waive it with a reason. scripts/check-platform-checklist.mjs enforces both directions: an unmapped kind fails CI (the platform grew a capability the checklist doesn't test), and an entry for a kind with no liveness ledger fails too (orphan, mirrors the liveness ORPHAN discipline). Pattern copied from examples/app-showcase/src/coverage.ts (demonstrated-or-waived, ADR-0060 house ledger style).", + "$comment": "Capability-coverage ratchet for the platform test checklist. The universe of governed metadata kinds is DERIVED at check time from packages/spec/liveness/*.json (the ADR-0049 ledger set) — this file must map EVERY kind to at least one checklist item, or waive it with a reason. scripts/check-platform-checklist.mjs flags both directions: an unmapped kind (the platform grew a capability the checklist doesn't test) and an entry for a kind with no liveness ledger (orphan, mirrors the liveness ORPHAN discipline). NOTE: this check runs on a MANUAL/periodic cadence (`pnpm check:platform-checklist`), not in per-PR CI — see docs/qa/platform-checklist/README.md 'Operating cadence'. Pattern copied from examples/app-showcase/src/coverage.ts (demonstrated-or-waived, ADR-0060 house ledger style).", "metadataKinds": { "action": { "items": [