Skip to content

Commit 94aeb1f

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-9632-published-readme-doc-links
2 parents 88b2093 + c07d6e8 commit 94aeb1f

43 files changed

Lines changed: 2852 additions & 87 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
feat(spec): `ActionSchema.onSuccess` — post-success navigation for `api`/`script` actions, with `${result.*}` joining the navigate template's interpolation scope (#9566, #9474)
6+
7+
<!-- adr-0087: not-required (accept-set expansion) One new CLOSED optional key
8+
on an existing shape; nothing authorable is renamed, retired or tombstoned, so
9+
there is no conversion to register. Previously-refused spellings stay refused —
10+
three of them now carry guidance pointing at the new key. -->
11+
12+
The maintainer's 2026-08-18 ruling (recorded on #9566, mirrored on #9474)
13+
declares ONE post-success navigation contract for both server-executing action
14+
types instead of two per-type conventions:
15+
16+
- `onSuccess: { navigate, openIn? }` — a strict object, read for
17+
`type: 'api'` and `type: 'script'` only (a refinement refuses it on
18+
`url`/`modal`/`flow`/`form`, where no success event exists for it to ride —
19+
the ADR-0078 posture, same enforcement shape as the `body`-on-non-script
20+
refinement).
21+
- `navigate` is a route/URL template. Its documented interpolation scope is
22+
`${param.*}` + `${ctx.*}` (existing) + **`${result.*}` — NEW: the action's
23+
server response payload** (an `api` action's response body, a `script`
24+
handler's return value), which is what makes "server clones a record → jump
25+
to the new record" declarable: `navigate: '/apps/crm/tasks/${result.id}'`.
26+
The interpolation ENGINE stays the renderer's (objectui `interpolateTarget`);
27+
the spec records the contract.
28+
- `openIn` is the closed enum `'self' | 'newTab'`, defaulting **`'self'`**
29+
(materialized, the file's default convention) — no general navigation DSL.
30+
- The shipped handler-return convention (`{ redirectUrl, openIn? }`,
31+
objectui#2967/#2904) keeps its 17.0.0 semantics: absent `openIn` still means
32+
new-tab (no silent behavior flip for existing handlers); a handler may return
33+
`openIn: 'self'` explicitly.
34+
35+
The console consumer is the downstream objectui half (SPA navigation branch,
36+
`executeAPI` navigation handling, `${result.*}` interpolation), filed
37+
Blocked-by these cards; the liveness ledger records the key at `planned`
38+
strength with the amend-on-landing instruction.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
"@objectstack/plugin-audit": patch
3+
---
4+
5+
Published README points at the `services.audit` reference again, in the form a published reader can follow (#9589)
6+
7+
PR #9531 dropped this README's "See Also" pointer to the runtime-services audit
8+
page because the page was measured wrong — it documented `record()` /
9+
`'set' | 'reset'` (the settings sink) as if that were the `audit` slot. PR #9587
10+
rewrote the page around the real slot, so the reason for the omission has stopped
11+
holding, and the link is restored.
12+
13+
It is restored because the page carries three things this README deliberately
14+
does not, each verified against the page as it stands on `main` rather than
15+
against the PR title that rewrote it:
16+
17+
- **the failure posture of the slot itself** — `recordAuthEvent` never throws; a
18+
failed ledger insert is reported at `error` level once per process and then
19+
drops to `debug`, the row is lost and nothing retries it, and the call silently
20+
no-ops when no data engine resolves or when `userId` is absent. This README
21+
documents the *record-view batcher's* two failure postures, which are a
22+
different code path; it says nothing about this one.
23+
- **the event's field-by-field shape** — that `userId` must be a real `sys_user`
24+
id, that `sessionId` lands on `record_id` with `object_name` fixed to
25+
`sys_session`, that `organizationId` stamps the tenant columns and an unstamped
26+
row is one non-administrator members can never see, and that `context` is
27+
serialized into `metadata`. This README states the slot's interface and its
28+
closed `'login' | 'logout'` action union, and deliberately stops there.
29+
- **the settings-sink disambiguation** — that `SettingsAuditSink.record()` is
30+
never registered as or resolved from this slot, and that
31+
`getService('audit').record({ ... })` therefore fails with a `TypeError`.
32+
33+
The restored line is **not** the line #9531 removed. That one read
34+
`[Audit Logging Best Practices](/content/docs/kernel/runtime-services/audit-service.mdx)`
35+
— a label describing a best-practices guide the page has never been, and a
36+
repo-path-rooted URL that resolves for neither of this README's published
37+
audiences. A README in the package's `files` array is rendered on npm and on
38+
GitHub, where a root-relative href resolves against `npmjs.com` / `github.com`,
39+
not against the docs site. The replacement uses the absolute
40+
`https://docs.objectstack.ai/docs/...` form that `create-objectstack`'s published
41+
READMEs already use, and its annotation states what the page adds — so the next
42+
author weighing the same omission can check the justification instead of
43+
reconstructing it.
44+
45+
The one pre-existing site-root-relative docs link in this same file
46+
(`/docs/permissions/permission-sets#access-depth...`, added by the same PR) is
47+
converted to the same absolute form. Its target page and heading anchor both
48+
exist; only the spelling was unfollowable off the docs site.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
Register `FLOW_CONVERSION_CONFLICT` (409) in the ADR-0112 error-code ledger under
6+
`@objectstack/metadata-protocol` (#9567). The code was already live on the wire —
7+
`saveMetaItem`'s flow-conversion rename guard (`protocol.ts`) has thrown it since
8+
ADR-0078 landed, already SCREAMING_SNAKE — but was invisible to
9+
`check:dispatcher-error-vocabulary`'s scan because the site stamps it through a
10+
cast (`(err as any).code = 'FLOW_CONVERSION_CONFLICT'`) rather than the bare-
11+
identifier `assign` shape the scan matched at the time. This is an ordinary,
12+
additive admission: no accept/reject behavior, no producer, and no wire shape
13+
changes.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
"@objectstack/service-cache": patch
3+
"@objectstack/service-job": patch
4+
---
5+
6+
fix(services): two published `.d.ts` JSDoc comments stop describing behaviour their code does not have — `MemoryCacheAdapter` eviction is FIFO, not LRU, and `recordRuns` is an on/off switch, not a retention cap (#9611)
7+
8+
Both comments are emitted by `tsup` into each package's built `index.d.ts`, so they
9+
are **the editor tooltip an npm consumer sees** — the same "published documentation
10+
asserting behaviour the runtime does not have" class as #9517 and #9532, in a third
11+
channel that no gate reads. No runtime behaviour changes in either package; this is a
12+
patch because the corrected text only reaches consumers through a release.
13+
14+
**1. `MemoryCacheAdapter` — "LRU-style eviction" was never LRU.**
15+
16+
The class comment advertised `TTL-based expiry and LRU-style eviction`. The eviction
17+
path takes `this.store.keys().next().value` — the first key in `Map` insertion order —
18+
and `get()` returns `entry.value` without ever deleting and re-setting the key, so a
19+
read does not move an entry back. Nor does an overwrite: `Map.set` on a key already
20+
present keeps its original insertion slot. Eviction has therefore always been
21+
**oldest-inserted (FIFO)**, which is a materially different hit-rate profile from the
22+
one the tooltip promised anyone sizing a cache.
23+
24+
The comment was corrected rather than the code, deliberately: `maxSize` defaults to `0`
25+
(unlimited), so the eviction path is off by default and nothing shipped is getting FIFO
26+
where it expected LRU, and there is no measured pull for LRU. Minting a real behaviour
27+
change to make a stale sentence true inverts the fix — the defect is that the
28+
documentation lies, not that the cache is wrong. (A real LRU already exists in the repo,
29+
`packages/metadata/src/utils/lru-cache.ts`, for the callers that need one.)
30+
31+
Four tests now pin the corrected sentence so it stops being an unenforced claim. Each is
32+
written as a **discriminator against LRU**: it reads (or overwrites) the oldest entry
33+
before overflowing the cache and then asserts that entry was evicted anyway — a hot key
34+
dies on age, an untouched newer key survives. The pre-existing eviction tests could not
35+
tell the two policies apart, which is how the wrong comment sat green.
36+
37+
**2. `DbJobAdapterOptions.recordRuns` — the comment described a different field.**
38+
39+
`/** Soft cap on sys_job_run rows recorded per job (defaults to none — handled by
40+
retention jobs) */` made three claims and the code contradicts all three: the field is a
41+
`boolean`, not a count; it defaults to `true`, not "none" (`args.options?.recordRuns ??
42+
true`); and it gates whether a `sys_job_run` row is written at all, rather than being
43+
trimmed later by retention. The sentence reads as if it belongs to the numeric
44+
`JobRunRetention` knob that ADR-0057 retired — a copy-paste that outlived its source.
45+
46+
The consequence the new wording keeps in sight: **a reader who sets `recordRuns: false`
47+
expecting "no cap" gets run history switched off.** The replacement states the real
48+
meaning (one row per attempt, inserted at start and updated on settle, default `true`)
49+
and both things that are *not* affected by the flag — the `sys_job` row's own
50+
`last_status` / `run_count` / `failure_count` counters, which `bumpJob` updates
51+
regardless, and `replay()`, which writes its synthetic `trigger: 'replay'` row without
52+
consulting the flag at all.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
Published agent-authoring prompts now reference real exports.
6+
7+
`prompts/create-new-project.md`, `prompts/implement-objectql.md` and
8+
`prompts/implement-objectos.md` told agents to import five symbols that
9+
`@objectstack/spec` does not export. Four failed loudly. The fifth did not:
10+
`import { Object } from '@objectstack/spec/data'` does not resolve, so the
11+
annotation in `export const AccountObject: Object = { ... }` bound to the
12+
**JavaScript global** `Object` instead — metadata authored from that prompt
13+
type-checked against a type that constrains nothing.
14+
15+
- Object definitions now use the house authoring convention measured in the
16+
example apps, `ObjectSchema.create({ ... })`, which genuinely validates.
17+
Correcting it exposed that the prompt's own example set `enable.audit` /
18+
`enable.workflow`, neither of which exists; they are now `trackHistory` /
19+
`files`, the pair the schema's own docstring uses.
20+
- `implement-objectql.md` keeps the real `Field` and `QuerySchema` imports and
21+
derives the object metadata type as `z.infer<typeof ObjectSchema>`, matching
22+
both `prompts/instructions.md` ("interfaces must be inferred from Zod") and
23+
spec's own `src/contracts/schema-driver.ts`.
24+
- `ManifestSchema` becomes `ObjectStackDefinitionSchema` from the package root:
25+
the prompt's subject is `objectstack.config.ts`, which is neither of the
26+
`/system` manifests.
27+
- `IdentitySchema` / `PolicySchema` have no bare referent; Rule #2 now names
28+
`RLSUserContextSchema` and `RowLevelSecurityPolicySchema` from
29+
`@objectstack/spec/security`.
30+
- The three non-existent "Key Files to Watch" paths
31+
(`system/{manifest,identity,events}.zod.ts`) now point at `stack.zod.ts`,
32+
`security/rls.zod.ts` and `kernel/events.zod.ts`.

‎.github/workflows/lint.yml‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,37 @@ jobs:
293293
- name: Governed-merges audit self-test
294294
run: pnpm check:pm-governed-merges
295295

296+
# Release-rehearsal clone preflight self-test (#9555). A local
297+
# `pnpm run version` rehearsal — the prescribed verification route for
298+
# every release-machinery change — HANGS FOREVER in an agent container:
299+
# every clone descends from a shallow checkout, so each `.changeset/*.md`
300+
# resolves to the parentless shallow boundary, and @changesets/git answers
301+
# a parentless add-commit by deepening and retrying in a loop with no
302+
# attempt limit — against a remote that gains it nothing and exits 0 while
303+
# doing so. Measured cost before diagnosis: ~2.5 h over two attempts,
304+
# because full CPU with no output reads as slow progress, not as a hang.
305+
# `scripts/pm/release-rehearsal-clone.mjs` is the refusal that turns that
306+
# into a one-second diagnosis, plus the repair for a throwaway clone.
307+
#
308+
# What runs HERE is the self-test, not the live check: the live check
309+
# judges whatever clone it is pointed at, and a CI checkout's depth is the
310+
# workflow's business rather than a verdict this job can hold. The
311+
# self-test pins BOTH directions on real git fixtures — the trapped shape
312+
# refuses, while a healthy tree AND a merely-shallow one whose changesets
313+
# sit after the boundary pass untouched (the predicate is the parentless
314+
# add-commit, never `--is-shallow-repository` alone) — and it pins the
315+
# WIRING, so unlinking the script from docs/releases-maintenance.md or
316+
# from this step reddens instead of going quiet. Temp-dir fixtures, no
317+
# network, ~1.7 s.
318+
#
319+
# Invoked as `node` rather than through a `pnpm check:*` alias: that alias
320+
# belongs in root package.json, which is declared territory of the
321+
# @changesets/cli v3 migration lane (#9465) while it runs. Same shape as
322+
# check-links.yml's ADR-link step, and dispatch-gates.mjs derives gate
323+
# families from either spelling.
324+
- name: Release-rehearsal clone preflight self-test
325+
run: node scripts/pm/release-rehearsal-clone.mjs --self-test
326+
296327
# Docs/skills authoring guard (#2035 / ADR-0059): TS code blocks in
297328
# Markdown/MDX are not type-checked or ESLinted, so skills/ and
298329
# content/docs/ can drift back to teaching the bare `: Page = {}` literal
@@ -487,6 +518,21 @@ jobs:
487518
- name: Quick-reference section counts match their tables
488519
run: pnpm check:quick-reference-counts
489520

521+
# #9604: the runtime-services chapter's pages are enumerated in three
522+
# hand-written places (meta.json "pages", the chapter list in
523+
# runtime-services/index.mdx, the services.* table in kernel/index.mdx) and
524+
# nothing read any of them -- check:docs-audit-scope derives WHICH pages the
525+
# accuracy audit covers, never whether an index enumerates them. #9604 found
526+
# services.sms with a page, a meta.json entry, a registered slot and a
527+
# canonical-source row, missing from BOTH index lists; #9588 was the same
528+
# page drifting on a different line. The pages on disk are the source of
529+
# truth and the gate holds all three enumerations (and the chapter list's
530+
# order) to them. It lives in this job with the other docs guards: the edit
531+
# that breaks it is a docs edit, so a packages/** paths filter would blind
532+
# it to its own failure mode.
533+
- name: Runtime-services indexes enumerate the chapter's real pages
534+
run: pnpm check:runtime-services-index
535+
490536
# #3723 ADR anchors: code an accepted ADR governs must keep naming it.
491537
# That incident reversed three accepted ADRs with a patch-level changeset,
492538
# and the mechanism was simply that the edited file never mentioned them —

‎content/docs/api/data-api.mdx‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,14 @@ Delete a record.
222222

223223
**Response**: `{ object: "account", id: "1", success: true }`
224224

225+
Every relation pointing at the deleted record honours its own `deleteBehavior`
226+
(`cascade` / `set_null` / `restrict`). On a `multiple: true` reference, `set_null`
227+
removes just the deleted id from the array and keeps the rest, and a reference set
228+
emptied that way reads back as `[]` — never `null`, so a client that branches on
229+
`null` for "no link" misses the emptied case (the `multiple` doc block in
230+
`packages/spec/src/data/field.zod.ts`, rendered in the
231+
[Field reference](/docs/references/data/field)).
232+
225233
---
226234

227235
## Batch Operations

0 commit comments

Comments
 (0)