Skip to content

Commit 7b35265

Browse files
committed
docs(qa): a checklist item drives the fresh-install order (os migrate apply, then the first os serve), and validation-rule-type-matrix drops its red-until-#24782 text
- cli.fresh-install-migrate-apply-first (new, r1): on an empty database `os migrate apply --yes` writes the organization-ownership birth marker (sys_migration adr-0131-organization-ownership), and the first `os serve` reaches its serving line with no ownership refusal. Ships in 18.1.0; a run against 18.0.0 records the refusal as that release's known behaviour. Driven live at base 688052e. - records-forms.validation-rule-type-matrix (r6 -> r7): knownGaps[1] and acceptance[5]'s "Red until #24782 lands" removed after a verification re-run of acceptance[5] at base 688052e read as #25051 fixed it. The history rows carry the placeholder ref #PENDING-25054; the next commit swaps in this PR's number and changes nothing else. Claude-Session: https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp Co-authored-by: Claude <noreply@anthropic.com>
1 parent 688052e commit 7b35265

2 files changed

Lines changed: 79 additions & 4 deletions

File tree

‎docs/qa/platform-checklist/areas/cli.json‎

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2273,6 +2273,76 @@
22732273
"ref": "#24850"
22742274
}
22752275
]
2276+
},
2277+
{
2278+
"id": "cli.fresh-install-migrate-apply-first",
2279+
"title": "A fresh install whose first command is `os migrate apply` boots on its first `os serve` (18.1.0): the apply creates the tables on an empty database and writes the organization-ownership birth marker (the sys_migration row `adr-0131-organization-ownership`), so the serve reaches its serving line with no ownership refusal",
2280+
"since": "v18.1",
2281+
"status": "active",
2282+
"revision": 1,
2283+
"priority": "P1",
2284+
"surface": "cli",
2285+
"personas": [
2286+
"operator (local shell) whose deploy script runs `os migrate apply` before the first `os serve` of a fresh install"
2287+
],
2288+
"fixtures": {
2289+
"app": "showcase",
2290+
"requires": [
2291+
"an EMPTY database: a file DB path under /tmp/RUN/ that does not exist yet — `test ! -e` on it and on its `-wal` and `-shm` before steps[0]. ⛔ Never a file an earlier command touched: `os dev`, `os serve` and `os migrate plan` each create tables in it, and a database that held a declared table before the apply was not created from empty, so it gets no birth marker by design",
2292+
"one environment for both commands, as a deploy script has: OS_DATABASE_URL=file:/tmp/RUN/fresh.db and OS_HOME=/tmp/RUN/home, plus OS_AUTH_SECRET and OS_SECRET_KEY (32-byte hex), because a production `os serve` composes the auth family only when an auth secret resolves and refuses to boot with no stable encryption key (RUNNER environment facts, the walled-boot entry). `os migrate apply` composes what `serve` mounts (packages/cli/src/utils/schema-migrate.ts header), so it gets the same environment. Add OS_PORT=PORT for the boot (RUNNER environment facts: the showcase's self-pinging connectors read it, `-p` alone does not set it)",
2293+
"a showcase directory no other runner boots: your own worktree, or a scratch copy. Both commands load its objectstack.config.ts and its compiled dist/objectstack.json, and both write .objectstack/data/ (the showcase_external fixture, local uploads) into the directory they run in. Delete that .objectstack after the run"
2294+
],
2295+
"knownGaps": [
2296+
"18.0.0 does not carry the fix. There `os migrate apply` on an empty database creates the tables and writes no birth marker, and the first `os serve` refuses the database as one 17.x or an 18.0 pre-release created: the refusal platform-core.v18-boot-refusals acceptance[1] reads, opening \"This database carries no organization-ownership marker\". A run whose subject is 18.0.0 records that refusal as 18.0.0's known behaviour, never as a FAIL of this item. The fix (#24827) is .changeset/24827-migrate-apply-birth-marker.md, a minor changeset pending on main, so it ships in 18.1.0. Read at base 688052e147: the fix commit 19484af538 is not an ancestor of the @objectstack/cli@18.0.0 tag commit 06cab4849a (exit 1; control leg, a commit 50 behind the tag, exit 0)",
2297+
"the fix's pins score no clause here (RUNNER rule 6, a pin scores only the layer it drives): packages/cli/src/commands/migrate/apply.organization-ownership-birth.integration.test.ts runs `MigrateApply` in-process and judges the guard through `assertOrganizationOwnershipMigrated` on a boot that creates nothing, and packages/cli/src/utils/schema-migrate.birth-after-flush.test.ts holds the failed-write refusal with no kernel at all. Neither spawns the CLI, so neither answers acceptance[0]'s exit code, and neither boots `os serve`, so neither reaches acceptance[1]'s serving line. The item carries no automated ref and both clauses are driven live",
2298+
"a Turso remote (`libsql://`) primary is outside this item: `os migrate apply` creates no tables there (its schema sync is skipped), so the first serving boot creates them and writes the birth marker itself, as it did before the fix"
2299+
]
2300+
},
2301+
"steps": [
2302+
"in the showcase directory, with the environment of fixtures.requires[1] exported and the DB path absent: `os migrate apply --yes; echo $?`. Keep the full output and the exit code",
2303+
"before any boot, read the marker through SQLite (node:sqlite, so a `-wal` is read too; there is no sqlite3 CLI here): `SELECT id, verified_at, applied_at, blocking, advisory, details FROM sys_migration WHERE id = 'adr-0131-organization-ownership'`",
2304+
"the first boot, in the same environment: `os serve -p PORT`, started with `setsid` and its output kept in a file (RUNNER environment facts: stop its process group, never the pnpm pid). Poll GET /api/v1/health until it answers 200 or the process exits, then read the output",
2305+
"read the marker row again with the steps[1] query, then stop the server's process group and confirm the port is free"
2306+
],
2307+
"acceptance": [
2308+
{
2309+
"clause": "`os migrate apply --yes` on an empty database creates the tables, exits 0, and writes the organization-ownership birth marker: exactly one sys_migration row `adr-0131-organization-ownership`, verified, whose details carry the birth at this release's ownership shape generation. The apply wrote it, before any boot",
2310+
"oracle": "build",
2311+
"verify": "steps[0] exits 0 and its output reports the tables it created. steps[1] returns exactly one row with verified_at set, applied_at null, blocking 0, and details.birth.shapeGeneration equal to ORGANIZATION_OWNERSHIP_SHAPE_GENERATION (packages/spec/src/system/migration.zod.ts#ORGANIZATION_OWNERSHIP_SHAPE_GENERATION, 1 at base 688052e147). The apply's output also prints `[organization-ownership] a datastore created from empty: birth marker written (shape generation N, platform V).` (packages/runtime/src/organization-ownership-boot-guard.ts#recordOrganizationOwnershipBirth); that line is supporting evidence only, the row is the oracle. steps[3] reads the same single row unchanged, so the boot found the apply's marker and wrote none of its own",
2312+
"evidence": "the apply's exit code and its created-tables line, and both reads of the marker row"
2313+
},
2314+
{
2315+
"clause": "the first `os serve` over that database boots: it reaches its serving line, with no organization-ownership refusal",
2316+
"oracle": "log",
2317+
"verify": "steps[2]'s output carries `✓ Server is ready` (packages/cli/src/utils/format.ts#printServerReady), not its DEGRADED variant, and GET /api/v1/health answers 200 while the process runs. The output has 0 hits for `carries no organization-ownership marker`, for `refuses to start` and for `os migrate organization-ownership`. A boot that exits 1 with that refusal is a FAIL on 18.1.0 or later, and 18.0.0's known behaviour (fixtures.knownGaps[0])",
2318+
"evidence": "the serving line and the boot's Mode row, the health status, and the three refusal greps with their counts"
2319+
}
2320+
],
2321+
"negative": [
2322+
"on a release that carries the fix, a fresh install whose first `os serve` after `os migrate apply` is refused as an earlier release's database is the defect #24827 fixed: FAIL",
2323+
"the other side of the birth decision, a database the apply did not create from empty, must get NO marker and stay refused until the ceremony runs. It is not this item's fixture: the fix's pin holds it (apply.organization-ownership-birth.integration.test.ts, case b), and the refusal itself is platform-core.v18-boot-refusals"
2324+
],
2325+
"traps": [
2326+
"destructive-in-place",
2327+
"first-boot-cold-start"
2328+
],
2329+
"source": [
2330+
".changeset/24827-migrate-apply-birth-marker.md (minor, pending on main at base 688052e147: ships in 18.1.0)",
2331+
"packages/cli/src/utils/schema-migrate.ts#recordBirthOfFlushedStore",
2332+
"packages/runtime/src/organization-ownership-boot-guard.ts#recordOrganizationOwnershipBirth",
2333+
"packages/runtime/src/organization-ownership-boot-guard.ts#ORGANIZATION_OWNERSHIP_BOOT_REFUSAL_CODE",
2334+
"packages/cli/src/commands/migrate/apply.organization-ownership-birth.integration.test.ts",
2335+
"docs/adr/0131-total-organization-ownership-no-null-organization-id.md",
2336+
"#24827 (the fixed p1), #25054 (the checklist gap: QA run #24838, round 16 of wave anchor #24579)"
2337+
],
2338+
"history": [
2339+
{
2340+
"revision": 1,
2341+
"date": "2026-10-21",
2342+
"change": "initial, for #25054: no checklist clause drove the fresh-install order #24827 fixed (QA run #24838, 18.0 sweep round 4; confirmed in round 16 of wave anchor #24579). A new item, because no existing item's scope covers a fresh install's first `os serve` after `os migrate apply`: platform-core.v18-boot-refusals scopes a 17.x-shaped database (its revision 6 says the order is not added there), cli.organization-ownership-ceremony the upgrade of a database 17.x or an 18.0 pre-release created, cli.migrate-plan-apply-json an apply on a database a prior `os dev` boot already synced (its fixtures.requires[0]), platform-core.boot-health and cli.dev-boot-contract the `os dev` boot, which creates the tables and writes the birth marker itself, and cli.scaffold-first-run the published scaffold booted by `os start --artifact`, with no `os migrate apply` in it. First evidence, driven live at base 688052e147 (the showcase in this PR's worktree, its own file DB, OS_PORT 41873): `os migrate apply --yes` exited 0, printed '✓ Created/extended 107 table(s)' and '[organization-ownership] a datastore created from empty: birth marker written (shape generation 1, platform 18.0.0).'; the marker row read verified_at 2026-10-21T06:54:23.840Z, applied_at null, blocking 0, advisory 0, details birth shapeGeneration 1, platformVersion 18.0.0; the first `os serve -p 41873` printed '✓ Server is ready' with Mode production, /api/v1/health and /api/v1/ready answered 200 (health 10 s after the start), the three refusal greps counted 0, and the row read back unchanged after the boot",
2343+
"ref": "#PENDING-25054"
2344+
}
2345+
]
22762346
}
22772347
]
22782348
}

‎docs/qa/platform-checklist/areas/records-forms.json‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1919,7 +1919,7 @@
19191919
"title": "All six validation-rule types enforce on the write path with their exact per-type error codes — and advisory severities flag without blocking",
19201920
"since": "v15",
19211921
"status": "active",
1922-
"revision": 6,
1922+
"revision": 7,
19231923
"priority": "P1",
19241924
"surface": "api",
19251925
"personas": [
@@ -1933,8 +1933,7 @@
19331933
"for the UNEVALUABLE legs (negative[0], negative[1], acceptance[5]): two runtime-authored scratch objects, each carrying one script rule whose condition `int(record.title) > 1` cannot be evaluated over a non-numeric title — no seeded rule is unevaluable, so the stock doors author them. As admin: PUT /api/v1/meta/object/qa_vr_probe {name: qa_vr_probe, label: QA, sharingModel: public_read_write, fields: {title: {type: text}}, validations: [{type: script, name: qa_unevaluable_warn, severity: warning, message: x, condition: \"int(record.title) > 1\"}]} → 200, and the same body as qa_vr_probe_err with the rule named qa_unevaluable_err at severity: error → 200 (the recipe of QA run #24779 / #24782). A write with title \"abc\" then drives each rule"
19341934
],
19351935
"knownGaps": [
1936-
"no showcase rule declares severity:'info' — it shares the identical non-error branch with 'warning' (rule-validator.ts tests `severity === 'error'` and logs everything else), so the warning leg is the driven proof; author a scratch info rule only if a distinct observation is wanted, and record which severities were actually driven",
1937-
"acceptance[5] stays red until #24782 lands: the unevaluable warning rule's write answers 201 and persists (correct), but the server log prints two lines naming the rule that both say \"write rejected\" — the predicate evaluator's fail-closed sentence, emitted before severity is consulted, and the advisory branch's warn, which embeds that violation message (packages/objectql/src/validation/rule-validator.ts). Measured at 580fd4ed (QA run #24779) and again at a43405d596 (#24802). When #24782's fix merges, re-run the clause and drop this line"
1936+
"no showcase rule declares severity:'info' — it shares the identical non-error branch with 'warning' (rule-validator.ts tests `severity === 'error'` and logs everything else), so the warning leg is the driven proof; author a scratch info rule only if a distinct observation is wanted, and record which severities were actually driven"
19381937
]
19391938
},
19401939
"variants": [
@@ -1987,7 +1986,7 @@
19871986
"evidence": "response + re-read + the captured log line"
19881987
},
19891988
{
1990-
"clause": "an UNEVALUABLE severity:'warning' rule lets the write land, and no log line beside it reports a rejection: the write answers 2xx, the row persists, and no server-log line naming the rule says the write was rejected or that the rule failed closed — the log must not state the opposite of what the server did. Red until #24782 lands",
1989+
"clause": "an UNEVALUABLE severity:'warning' rule lets the write land, and no log line beside it reports a rejection: the write answers 2xx, the row persists, and no server-log line naming the rule says the write was rejected or that the rule failed closed — the log must not state the opposite of what the server did.",
19911990
"oracle": "log",
19921991
"verify": "step-7 qa_vr_probe write: status 201 and a re-read finding the row; then every log entry naming qa_unevaluable_warn, read to its end — the predicate evaluator's entry spans lines (it embeds the CEL source with a caret) and its verdict sits after the caret — none may contain \"write rejected\" or \"fails closed\" (the fail-closed sentence belongs to the error-severity path; rule-validator.ts header: an unevaluable warning/info rule is logged, never thrown). Score the absence of the rejection wording, not whichever replacement sentence #24782 picks; a line saying the rule could not be evaluated is expected and passes. Control: the qa_vr_probe_err write answers 400 VALIDATION_FAILED naming qa_unevaluable_err and persists nothing — there the rejection wording is true",
19931992
"evidence": "both responses + both re-reads + every log line naming either rule, quoted"
@@ -2045,6 +2044,12 @@
20452044
"date": "2026-10-20",
20462045
"change": "steps[0] respelled: 'boot showcase isolated' → 'boot the showcase on its own port and DB'. Round 12's out-of-scope finding (landing record 6287983829 on wave anchor #24579): the phrase 'showcase isolated' named the dogfood §0 own-port boot, and a runner read it as the `isolated` tenancy posture (OS_TENANCY_POSTURE=isolated). The new spelling names that boot by what it is, in RUNNER.md's own words for it (the trap table's counter: own port/DB, skill §0), and names no posture. Read at base e4d604efd3. No clause changed",
20472046
"ref": "#25003"
2047+
},
2048+
{
2049+
"revision": 7,
2050+
"date": "2026-10-21",
2051+
"change": "fixtures.knownGaps[1] removed and acceptance[5].clause's closing 'Red until #24782 lands' dropped, as knownGaps[1] said to do once #24782's fix merged: PR #25051 (72518d20f5), named by the domain:engine pointer 6308955528 on seat post #6023. Verification re-run of acceptance[5] at base 688052e147, steps[6] on the canonical own-port boot (`objectstack dev --ui --seed-admin -p 41874 -d file:SCRATCH/data.db`, OS_PORT exported): the qa_vr_probe write {title: \"abc\"} answered 201 and the re-read found its one row; exactly one server-log line names qa_unevaluable_warn, \"Validation rule 'qa_unevaluable_warn' (warning): the rule could not be evaluated (runtime: int() type error: cannot convert to int) — write proceeded: the rule is advisory, so it was not applied to this write\", and it carries neither 'write rejected' nor 'fails closed'. Control: the qa_vr_probe_err write answered 400 VALIDATION_FAILED, fields[0].constraint {rule: qa_unevaluable_err, reason: unevaluable}, its re-read found no row, and its log lines say the write was rejected. No other clause changed",
2052+
"ref": "#PENDING-25054"
20482053
}
20492054
]
20502055
},

0 commit comments

Comments
 (0)