Skip to content

Commit 0ac71af

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-10877-self-test-wiring-presence
2 parents b5ef0d6 + 13667c4 commit 0ac71af

17 files changed

Lines changed: 1328 additions & 129 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
"@objectstack/objectql": patch
3+
---
4+
5+
The Archiver resolves its window through ADR-0057 P4 governance (#10528).
6+
`LifecycleService.archiveObject` read `archive.after` — and, since #10347,
7+
`ttl.expireAfter` — straight off the declaration, so for any object declaring
8+
`lifecycle.archive` an operator's settings override was silently ignored, a
9+
registered `LifecycleRetentionFloor` was never evaluated, and per-tenant windows
10+
did not apply.
11+
12+
This was not a forgotten call. `reapObject` **returns** into `archiveObject` for
13+
any object declaring `archive`, so the three `effectiveWindowMs` resolutions on
14+
the reap path sat on a branch archive-declaring objects skip entirely — which is
15+
why the divergence was total rather than partial, and why threading an override
16+
into the cutoff alone would still have left floors and tenant windows unreached.
17+
18+
All three legs now run, through the same resolver the Reaper uses:
19+
20+
- a per-object `retention_overrides` entry beats the declaration, on the key that
21+
matches which window the selection picked — `expireAfter` for a ttl-selected
22+
archive, `maxAge` for an age-selected one;
23+
- an override below a registered floor is rejected (the declared window stands),
24+
logged at `error` naming the registrar, consequence and fix, and recorded in
25+
`report.floorViolations` — the leg whose absence was *silent*, since an empty
26+
`floorViolations` is indistinguishable from a healthy sweep. A *declared*
27+
archive window below a floor is reported the same way and still enforced;
28+
- tenant-scoped windows issue one candidate read per overriding tenant, then one
29+
global pass covering everyone else including NULL-org rows — the shape `reap()`
30+
already used, with tenant overrides going through the same floor.
31+
32+
Unchanged on purpose: #10347's cutoff **selection** (a declared `ttl` still
33+
decides which rows move, on `ttl.field`); the retain-first posture (no archive
34+
datasource ⇒ `archive-pending`, hot-delete only what the cold store took); the
35+
per-batch abort checks, now the first act of every pass; and the cold-side
36+
`archive.keep` prune, which bounds the archive rather than the hot store and has
37+
no settings key. An object with no override and no floor sweeps exactly as
38+
before, as one pass over exactly the predicate it ran before.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
"@objectstack/cli": patch
3+
---
4+
5+
Correct a false verb in `os migrate meta`'s own source comments: the `--from`
6+
arm **lists** the mechanical edits an author's source needs; it rewrites no file
7+
(#10831).
8+
9+
The `pendingDataMigrations` docblock in
10+
`packages/cli/src/commands/migrate/meta.ts` opened with "this command rewrites an
11+
author's source" — 74 lines above the command header that says the opposite
12+
("The command does not silently rewrite TS config source (that AST rewrite is
13+
unsafe and lossy)"). Both `writeFileSync` calls in the file are guarded by
14+
`if (flags.out)`, so the only file the `--from` arm ever writes is the `--out`
15+
JSON snapshot. The in-place codemod is a separate, unbuilt piece of work.
16+
17+
The contrast the docblock was drawing — metadata migration's subject is the
18+
author's *source*, the two data migrations' subject is a deployment's *rows*
19+
is correct and is preserved; only the verb on the first half changed. The
20+
`--stored` arm genuinely does rewrite `sys_metadata` rows and its wording is
21+
untouched.
22+
23+
No runtime behaviour changes: comment-only.

.github/workflows/lint.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,28 @@ jobs:
11761176
- name: objectui pin-changeset digest guard
11771177
run: pnpm check:objectui-changeset
11781178

1179+
# objectui pin write-ordering guard (#10797). Its sibling above is about
1180+
# WHAT the bump writes; this one is about WHEN. `bump-objectui.sh` wrote
1181+
# `.objectui-sha` before it read the commit subject out of the objectui
1182+
# checkout, so a commit object that cannot be read killed the run under
1183+
# `set -e` with the pin file ALREADY REWRITTEN — no changeset, no commit,
1184+
# a bare `fatal: bad object` as the whole explanation, and a tree the
1185+
# operator had to clean up by hand. Re-running did not self-correct: the
1186+
# pin file now held the bad SHA. Reachable with no argument at all —
1187+
# measured, git 2.43.0, `git rev-parse HEAD` exits 0 and prints the sha
1188+
# for a commit whose object is missing, because it resolves the ref
1189+
# without reading the object.
1190+
#
1191+
# The self-test asserts the invariant on BYTES, not on the message: both
1192+
# the broken and the fixed script exit non-zero on this input (128 from
1193+
# `set -e` vs 1 from the refusal), so "did it fail?" does not tell them
1194+
# apart and a test asking only that would have passed against the defect.
1195+
# It also drives two readable-commit cases all the way through, because
1196+
# `exit 1` at the top of the script would satisfy every refusal case on
1197+
# its own. Offline: throwaway git repos, no node, no network, ~1s.
1198+
- name: objectui pin write-ordering guard
1199+
run: pnpm check:objectui-bump
1200+
11791201
# Changeset-family gate self-tests (#6509). The SELF-TEST halves only —
11801202
# the real scans stay in pr-automation.yml's `changeset-check`, and the
11811203
# split is the whole point of this step.
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,10 @@ function getPreferredLanguage(request: NextRequest): string {
4545
}
4646

4747
/**
48-
* Middleware for automatic language detection and redirection
48+
* Proxy (Next `proxy` file convention) for automatic language detection and
49+
* redirection
4950
*
50-
* This middleware:
51+
* This proxy:
5152
* - Detects the user's preferred language from browser settings or cookies
5253
* - Redirects users to the appropriate localized version
5354
* - For the default language (en): keeps URL as "/" (with internal rewrite)
@@ -56,7 +57,7 @@ function getPreferredLanguage(request: NextRequest): string {
5657
* The docs are English-only by decision (2026-07); the i18n plumbing stays so a
5758
* future language only needs entries in lib/i18n.ts and content, not new routing.
5859
*/
59-
export default function middleware(request: NextRequest) {
60+
export default function proxy(request: NextRequest) {
6061
const { pathname } = request.nextUrl;
6162

6263
// Check if the pathname already has a locale

content/docs/automation/flows.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,9 @@ Replace them per branch — they are different mechanisms, not one rename:
242242
| `actionType: 'my_fn'` (shorthand) | `function: 'my_fn'` — the conversion moves it for you |
243243
| inline `config.script` | move the logic into a registered function and call it via `config.function` |
244244

245-
Stored flows are rewritten by `os migrate meta --from 16`; authoring one of
246-
these keys in TypeScript is now a compile error carrying the same prescription.
245+
Run `os migrate meta --from 16` to list the mechanical edits for existing flow
246+
sources; apply them by hand. Authoring one of these keys in TypeScript is now a
247+
compile error carrying the same prescription.
247248

248249
[#4343]: https://github.com/objectstack-ai/objectstack/issues/4343
249250

content/docs/data-modeling/queries.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -385,8 +385,8 @@ backend, which left "what can this backend actually compute" unpredictable to th
385385
Both were retired (#6188, ADR-0049 enforce-or-remove): a query carrying either is now
386386
refused at parse with a prescription. There is no replacement in the query vocabulary —
387387
read the rows with an ordinary `fields` query and shape them in the caller, or materialise
388-
the roll-up as a stored field. `os migrate meta --from 16` rewrites affected dataset
389-
measures.
388+
the roll-up as a stored field. Run `os migrate meta --from 16` to list the mechanical
389+
edits for the affected dataset measures; apply them by hand.
390390
</Callout>
391391

392392
<Callout type="info">

content/docs/deployment/cli.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -919,9 +919,9 @@ an old dialect exits `1`. So "my metadata is on protocol N" becomes a check
919919
rather than a belief.
920920

921921
Note the division of labour with the default mode: `os migrate meta --from N`
922-
rewrites an **author's source** and reads no database; `--stored` rewrites **one
923-
deployment's rows** and reads no config. Same chain, opposite ends of the
924-
contract — which is why the two modes are mutually exclusive.
922+
lists the edits **an author's source** needs and reads no database; `--stored`
923+
rewrites **one deployment's rows** and reads no config. Same chain, opposite
924+
ends of the contract — which is why the two modes are mutually exclusive.
925925

926926
**Without shell access, use the route.** This command needs to reach the
927927
deployment's database directly, which a hosted operator cannot do. The same pass

content/docs/protocol/objectql/query-syntax.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,8 @@ by no SQL backend, so which backend could compute them was unpredictable to the
857857
Both were retired (#6188, ADR-0049 enforce-or-remove) and are now refused at parse with
858858
a prescription. There is no replacement in the query vocabulary — read the rows with an
859859
ordinary `fields` query and shape them in the caller, or materialise the roll-up as a
860-
stored field. `os migrate meta --from 16` rewrites affected dataset measures.
860+
stored field. Run `os migrate meta --from 16` to list the mechanical edits for the
861+
affected dataset measures; apply them by hand.
861862
</Callout>
862863

863864
### Group By Multiple Fields

docs/releases-maintenance.md

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,10 @@ question and **warns** — it does not refuse:
6262
* **Not on `origin/main`** — a loud warning naming the branch(es) the commit *is*
6363
on, and saying which situation it is: pushed onto a branch that never merged, or
6464
never pushed at all. The pin is still written; deciding is yours.
65-
* **Cannot be answered** — no `origin/main` in the checkout, or the commit object
66-
is absent — it says *that*, and never borrows the wording of either verdict.
67-
(`git merge-base --is-ancestor` exits **128** on an absent object: an error, not
68-
a "no". And `git rev-parse HEAD` exits 0 for a commit whose object is missing,
69-
so the pin arriving is no proof the object is there.)
65+
* **Cannot be answered** — no `origin/main` in the checkout — it says *that*, and
66+
never borrows the wording of either verdict. (`git merge-base --is-ancestor`
67+
exits **128** rather than returning a verdict when it cannot read an object: an
68+
error, not a "no".)
7069

7170
It is a warning rather than a gate on purpose: `origin/main` is only as fresh as
7271
your last fetch, so a hard failure here would reject a legitimately-just-merged
@@ -78,6 +77,37 @@ though, a bad pin merges and `pnpm sdui:manifest` below would ratchet
7877
spec↔registry parity against a tree that is not on main — which is why the
7978
producer half warns at all.
8079

80+
#### A commit object it cannot read is refused, not warned about (#10797)
81+
82+
The bullets above are about a pin that is not on `main` — a real commit you can
83+
still meaningfully pin. A pin whose commit **object cannot be read at all** is a
84+
different thing: there is nothing to pin, and no changeset entry or commit message
85+
can be derived from it. That refuses, and it refuses **before writing anything**:
86+
87+
```
88+
✗ REFUSING to bump: the objectui commit object <short> cannot be read in <path>.
89+
90+
NOTHING WAS WRITTEN — .objectui-sha is untouched and still holds the old pin.
91+
```
92+
93+
`.objectui-sha` is byte-identical to what it was before the run — a failed bump
94+
leaves no half-applied state. It used to: the pin file was written *before* the
95+
run read the commit subject, so `git log` failing killed the script under `set -e`
96+
with the file already rewritten, no changeset, no commit, and a bare
97+
`fatal: bad object` as the whole explanation. Re-running did not self-correct,
98+
because the pin file now held the bad SHA.
99+
100+
You reach this with no argument at all — `git rev-parse HEAD` exits **0** and
101+
prints the sha even when that commit's object is missing from the store, since it
102+
resolves the ref without reading the object. A partial clone that has not fetched
103+
the object, or an interrupted object store, is enough. The remedy the message
104+
names is `git -C <objectui> fetch origin`, then re-run the bump.
105+
106+
`pnpm check:objectui-bump` (`scripts/bump-objectui.selftest.sh`, run by the lint
107+
job) pins this: it asserts the file's **bytes** across a refused run, because the
108+
broken and the fixed script both exit non-zero here and only the file tells them
109+
apart.
110+
81111
#### After the pin moves: run the declaration-parity ratchet (#5960)
82112

83113
The bump has a second half, and it is not optional:

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
"check:console-injection": "node scripts/check-console-injection.mjs --self-test && node scripts/check-console-injection.mjs",
8787
"check:dev-prereqs": "node scripts/check-dev-prereqs.mjs --self-test && node scripts/check-dev-prereqs.mjs",
8888
"check:objectui-changeset": "node scripts/objectui-changeset-digest.mjs --self-test && node scripts/objectui-range.mjs --self-test",
89+
"check:objectui-bump": "bash scripts/bump-objectui.selftest.sh",
8990
"check:prerelease-pins": "node scripts/check-prerelease-pin-watch.mjs --self-test && node scripts/check-prerelease-pin-watch.mjs",
9091
"check:empty-changeset": "node scripts/check-empty-changeset.mjs --self-test && node scripts/check-empty-changeset.mjs",
9192
"check:adr-0087-registration": "node scripts/check-adr-0087-registration.mjs --self-test && node scripts/check-adr-0087-registration.mjs",

0 commit comments

Comments
 (0)