Skip to content

Commit fff7ed7

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-22269-print-render-service
2 parents fb36ebb + 0cfd0c4 commit fff7ed7

71 files changed

Lines changed: 3308 additions & 686 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/service-job': patch
4+
---
5+
6+
feat(spec)!: a job `retryPolicy` block whose `maxRetries` is omitted or 0 is refused, and `retry-policy-converged` leaves the load path
7+
8+
Clause-②: yes (narrowing)
9+
10+
**BREAKING (authoring):** `maxRetries` has defaulted to 0 since 17, so a `job.retryPolicy` block that omits it, or writes 0, buys no retry: the job runs once. Through 17 the loader hid that by writing the pre-17 `maxRetries: 3` and `backoffMultiplier: 2` into such a block with a notice. At 18 the ADR-0087 D2 conversion `retry-policy-converged` is retired from the load path (`retiredAfter: '17.7.0'`), and the job parse refuses the block at `retryPolicy.maxRetries`, at every door that parses an authored job (`defineStack`, `os validate`, `os build`, and `os lint` when the config calls it): "A retry block with no attempts is no retry block: state `maxRetries` ≥ 1, for example 3, or delete the block." The rule is the job's, the same one `flow.errorHandling` applies to `strategy: 'retry'`; the shared `RetryPolicySchema` is unchanged, so a `try_catch` node's `retry` region is judged as before.
11+
12+
Migration:
13+
14+
- `retryPolicy: {}` or `retryPolicy: { backoffMs: 5000 }` → `retryPolicy: { maxRetries: 3, backoffMs: 5000 }` (3 is the count such a block ran with before 17), or delete the block if the job should run once.
15+
- `retryPolicy: { maxRetries: 0, … }` → delete the block: a job with no `retryPolicy` is a single attempt.
16+
17+
What an author who still writes it sees: `defineStack` throws `STACK_SCHEMA_INVALID` (422) with one issue at `jobs.N.retryPolicy.maxRetries` carrying the prescription above, and `os validate` prints the same issue. Nothing is rewritten silently any more.
18+
19+
Not refused: a block that states `maxRetries` and omits `backoffMultiplier`. It keeps the declared default of 1, a flat delay, where the loader used to write 2. State `backoffMultiplier: 2` to keep the old exponential curve.
20+
21+
Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. For such a block the edit writes both pre-17 numbers (`maxRetries: 3`, `backoffMultiplier: 2`). The D3 entry `job-retry-policy-without-attempts-refused` states the decision left to you: keep the written count, or delete the block.
22+
23+
The flow half of the conversion (`retryDelayMs` → `backoffMs` on `flow.errorHandling` and a `try_catch` node's `retry`) needed nothing more: both surfaces already refuse `retryDelayMs` with the rename to `backoffMs`.
24+
25+
Stored metadata needs no edit. Stored-row rehydration, the automation engine's flow rehydration, the metadata save door's flow fallback and the artifact-ingestion door inside its window still replay the conversion, so a job row stored without a count keeps loading with the three retries it ran with.
26+
27+
`@objectstack/service-job`: a comment-only change in `runWithPolicy`, whose `maxRetries` default is now reached only by a caller that builds the options by hand.
28+
29+
<!-- adr-0087: registered job-retry-policy-without-attempts-refused -->
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@objectstack/plugin-audit': minor
3+
'@objectstack/cli': minor
4+
---
5+
6+
feat(plugin-audit): `sys_activity` rows carry two writer-stamped read-rule columns, and the withheld-row rule is a predicate over them, with no SYSTEM pre-scan (ADR-0139 E6)
7+
8+
**Run `os migrate activity-changed-fields --apply` at once after upgrading.** After the upgrade, and until it runs, the `sys_activity` rows written before the upgrade carry no stamp and are withheld from every non-system reader: activity feeds, the header bell and record timelines show only the rows written since the upgrade. The door is `os migrate activity-changed-fields`, dry run by default, `--apply` to write, idempotent, and it stamps both columns in one run. Its flags are `os migrate audit-metadata-bodies`'s, one for one (`--database-url`, `--apply`, `--yes`, `--force`, `--json`). The columns arrive with the upgraded deployment's schema sync, which an `--apply` run performs itself; a dry run against a database no upgraded process has opened cannot read them and exits `1`.
9+
10+
Clause-②: yes
11+
12+
`sys_activity` gains two columns, stamped at write time by one derivation (`planActivityStamp`): the CRUD mirror stamps its own rows, and a `sys_activity` before-insert hook stamps every row any other writer inserts (an app's own server action, the seed loader), over whatever value that writer sent.
13+
14+
- `changed_fields` (`tags`) — the key set of the recorded change: for an update, every field its recorded `old` / `new` values name; `[]` for a create, a delete, a note, or an update that touched only `internal` fields. It is served narrowed to the fields the reader is served.
15+
- `milestone_key` (`text`) — for a fired activity milestone, one sorted, comma-joined key of every field the object's `activityMilestones` declaration watched when it fired; `null` for every other row. It is never served.
16+
17+
The withheld-row rule is now a WHERE over those two columns, one branch per parent object, on `find`, `findOne`, `count` and `aggregate`. It reads no row, so the 2,000-row SYSTEM pre-scan and its window are gone: a broad read of the stream is served every row the caller may read, with a true `total`. What the rule withholds:
18+
19+
- an update row whose `changed_fields` is non-empty and names no field the reader is served (unchanged);
20+
- a fired milestone row, unless its key equals the key of the object's CURRENT `activityMilestones` declaration and the reader is served every field that declaration watches. **The cost:** a milestone row written under an earlier declaration, or naming a since-removed field, is withheld from every non-exempt reader until retention ages it out (14 days declared; `lifecycle.retention_overrides` may lengthen it);
21+
- an unstamped row, from every non-system reader: one at rest from before the upgrade until the backfill stamps it, and one the planner cannot judge (a milestone row naming no readable watched-field list), which stays unstamped.
22+
23+
The parent-field query guard covers both columns as it covers `metadata`: a reader withheld any field of the parent is refused a filter, sort, group or aggregate over `changed_fields` or `milestone_key` (`PERMISSION_DENIED` / 403).
24+
25+
`@objectstack/plugin-audit` exports the backfill, `backfillActivityChangedFields` (with `ACTIVITY_BACKFILL_PAGE_SIZE` and its report and engine types), and the planner, `planActivityStamp` (with `ACTIVITY_CHANGED_FIELDS_COLUMN`, `ACTIVITY_MILESTONE_KEY_COLUMN` and `ActivityStamp`). Inside the module (none of it is exported from the package), `computeWithheldUpdateFilter` keeps its name and now builds the predicate, `isWithheldOnlyUpdate` keeps its name as the update half over `changed_fields`, and `isWithheldMilestone` / `isWithheldRow` are gone with the pre-scan they judged for.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
Clause-②: no
6+
7+
The published TSDoc now sends a form section's translations to the right key. `FormSectionSchema.name` used to name only `metadataForms.<type>.sections.<name>`, which is the key for a metadata-editing form (`defineForm({ schemaId })`, read by `resolveMetadataFormLabels`). A section of an object-bound form view is keyed at `objects.<object>._sections.<name>`, where `<object>` is the form's own `data.object`, else its view's. The docblock now names both keys and which form each one serves. `TranslationDataSchema._sections` now names the three surfaces that author its key (form-view sections, `record:details` sections, object `fieldGroups`) and its readers (`walkObjectSections`, `collectViewRecord`, `translateView`). `resolveMetadataFormLabels`' docblock no longer says that a section without a `name` keeps its section-level text: such a section is looked up under a slug of its `label`, as the function has always done. These are comment changes only: no key, `.describe()` text or behaviour changes. An object-form translation bundle written from the old docblock under `metadataForms.…` never translated anything. Move its section entries to `objects.<object>._sections.<name>`.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/platform-objects': patch
4+
---
5+
6+
Clause-②: no
7+
8+
The `description` of four metadata types in `DEFAULT_METADATA_TYPE_REGISTRY`, which Setup shows under the
9+
type's title, is now one plain sentence for an administrator instead of ADR vocabulary:
10+
11+
- `api`: "A stable HTTP URL that other systems call to run a flow or work with records in the app"
12+
- `doc`: "A help page written in Markdown that a package ships or you write yourself"
13+
- `book`: "A table of contents that groups help pages into ordered sections"
14+
- `capability`: "A permission a package defines; you grant it through permission sets"
15+
16+
The ADR references (ADR-0121, ADR-0046, ADR-0046 §6, ADR-0066 D1) moved to the comments beside the
17+
registry rows. The generated metadata-forms translations carry the new sentences in en, zh-CN, ja-JP
18+
and es-ES. No key, type or accept/refuse result changes.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/plugin-auth': patch
3+
---
4+
5+
Clause-②: no
6+
7+
A session minted by `IAuthService.mintPrincipalSession` is no longer refreshed by a read. Before,
8+
`get-session` (and every endpoint that reads the session) still renewed it on each request: #23772's
9+
guard held its expiry, but the row's `updatedAt` was rewritten, the session cookie was re-set with a
10+
7-day `Max-Age`, and `bearer()` answered `set-auth-token`. The console reads that header as a token
11+
rotation, so a browser carrying a minted cookie stayed on "Opening your console…" and the print
12+
service could not render under the requesting user. A request that carries a minted session now
13+
sets better-auth's own per-request skip-refresh switch, on the cookie lane, the bearer lane and
14+
in-process. The mint's contract, TTL, refusals and revoke are unchanged.

‎content/docs/automation/jobs.mdx‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -385,13 +385,15 @@ Delay before retry *n* is `min(backoffMs * backoffMultiplier^(n-1), maxRetryDela
385385
optionally jittered. `maxRetries` counts retries **after** the initial attempt and
386386
is capped at 10.
387387

388-
Two defaults worth knowing before you rely on the block:
388+
Two rules worth knowing before you rely on the block:
389389

390-
- **`maxRetries` defaults to `0`** — declaring `retryPolicy` without stating a
391-
count still means *no retry*. State a count to opt in.
390+
- **A block must state `maxRetries` ≥ 1.** A `retryPolicy` that omits it or
391+
writes `0` would buy no retry, so it is refused at `objectstack validate` with
392+
"a retry block with no attempts is no retry block". State a count, or delete
393+
the block: a job with no `retryPolicy` runs once.
392394
- **`backoffMultiplier` defaults to `1`** — a flat delay, not exponential.
393395

394-
Those defaults are why the block is closed: a key it does not declare — `maxRetry`
396+
The count rule and that default are why the block is closed: a key it does not declare — `maxRetry`
395397
for `maxRetries`, a `maxDelayMs` borrowed from another retry vocabulary — is
396398
refused at `objectstack validate` with the declared key it was near, rather than
397399
dropped and replaced by a default that never retries.

‎content/docs/deployment/cli.mdx‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,6 +1172,7 @@ where the data lives.
11721172
| `os migrate value-shapes` | Scan stored reference and structured-JSON field values against the platform's value contract, and record the deployment's migration flag when clean |
11731173
| `os migrate summary-nulls` | Backfill roll-up `count` / `sum` columns still stored as `NULL` on parent rows created before the insert-time seed. Repairs values; no flag, nothing depends on it having run |
11741174
| `os migrate comment-reactions` | Carry the emoji reactions a 17.x deployment stored in the retired `sys_comment.reactions` column into `sys_comment_reaction` records, list every stored value that does not read, and record the verified run that lets `os migrate apply --allow-destructive` drop the column |
1175+
| `os migrate activity-changed-fields` | Stamp the two read-rule columns (`changed_fields`, `milestone_key`) on the `sys_activity` rows written before they existed. Until it runs, those rows are withheld from every reader but the system, so run it at once after upgrading. No flag; idempotent |
11751176
| `os migrate meta --stored` | Replay the metadata conversion chain over this deployment's `sys_metadata` rows and rewrite the ones still carrying a pre-protocol shape. Hygiene, not a gate — nothing depends on it having run |
11761177
| `os migrate security-catalog-overlays` | List the environment-wide permission-set and position rows (the legacy plurals `permissions` / `positions` included) stored over a name a configured package holds — the rows a v18 cold boot refuses — each with the package that holds its name; `--apply` deletes them, one history tombstone per row. Run it before the first v18 boot, with the flags and environment the deployment boots with: the platform security plugin's sets are held only where `os serve` composes it (an auth secret set, and the `auth` tier on), and the step takes `serve`'s `--preset` and `--dev` with `serve`'s meaning. Adopts nothing; exits 1 while it lists a row |
11771178
| `os migrate duplicates` | Report business identifiers already minted twice across the organization partitions, and the rows blocking the boot-time NULL-safe index tightenings — a read-only inventory as JSON on stdout. Renumbers nothing and writes no row and no schema; run it before the boot-time tenancy repair, which overwrites part of the evidence |
@@ -1411,6 +1412,36 @@ A run that lists an unreadable value or fails a write exits `1`, and so does an
14111412
A database created on v18 never had the column: the run finds nothing, and
14121413
there is no drop to wait for.
14131414
1415+
#### `os migrate activity-changed-fields`
1416+
1417+
An activity row is served to a reader only when the platform can judge it for
1418+
that reader: whether the change it records touched a field the reader may see,
1419+
and, for a fired milestone, whether the reader may see every field the
1420+
milestone watches. Since this version that judgement reads two columns the
1421+
platform stamps on every new row, `changed_fields` and `milestone_key`. Rows
1422+
written before the upgrade carry no stamp, and **an unstamped row is withheld
1423+
from every reader but the system**, so until this command runs, activity feeds
1424+
and record timelines show only the rows written since the upgrade.
1425+
1426+
```bash
1427+
os migrate activity-changed-fields # Dry run: counts, writes no rows
1428+
os migrate activity-changed-fields --apply # Stamp the rows (prompts)
1429+
os migrate activity-changed-fields --apply --yes --json # CI / scripts
1430+
```
1431+
1432+
Each row is stamped from its own recorded change, by the rule the platform
1433+
stamps new rows with, and both columns are stamped in one run. **A row that
1434+
cannot be judged stays unstamped**, and stays withheld: a milestone row that
1435+
names no readable list of watched fields. The report counts those rows; they
1436+
age out with the object's retention (14 days unless the deployment lengthens
1437+
it).
1438+
1439+
**Idempotent**: only unstamped rows are read, so a second run stamps nothing,
1440+
and re-running is how an interrupted run is finished. The columns arrive with
1441+
the upgraded deployment's schema sync, which an `--apply` run performs itself:
1442+
a dry run against a database no upgraded process has opened cannot read them,
1443+
reports the failed read and exits `1`. A failed read or write exits `1`.
1444+
14141445
#### A database created by this version needs no migration
14151446
14161447
A deployment whose database the platform **creates from empty** records these

0 commit comments

Comments
 (0)