You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 290a435
Browse filesBrowse the repository at this point in the historyBrowse files
Merge origin/main into claude/project-thread-g33c7k
Brings in main's re-measured authz census (#26406), the fix for main's
own dogfood red (#26400) that the full battery on this branch showed.
Main's automation skill change (#26390) left the approvals reference
two bytes under its token ceiling, so the queue approver row is
shortened to fit (members of the named ownership queue at node entry;
an empty queue adds nobody).
Claude-Session: https://claude.ai/code/session_01GTntg542RUmmRRbMY6wMZD
Co-authored-by: Claude <noreply@anthropic.com>
**BREAKING** — the two unread dependency spellings leave the authoring surface: `PackageDependency.optional` (an entry of the marketplace snapshot's `manifestJson.dependencies[]`) and `AppManifest.dependencies` (the bare-id list on the app install manifest) are `retiredKey()` tombstones (#25792, ADR-0141 D8 items 2 and 3).
6
+
7
+
A `major`-class change, recorded as `minor` under the launch-window convention. ADR-0141 D8 left both fates to "a measured reader count"; the count was zero for both, so both retire at once, with no retirement stage (ADR-0049 enforce-or-remove).
8
+
9
+
Clause-②: no (narrowing)
10
+
Metadata-protocol: narrowed
11
+
12
+
**Why.** A dependency is declared in one place, the package's `manifest.dependencies` record (`{ [packageId]: versionRange }`), and the ADR-0141 D2 pre-install check reads that record alone and treats every entry as required. Two more spellings stayed declared beside it and nothing read either. `optional: true` promised "missing dep is a warning, not an error", but the record has no optional arm, so no install path could honour it. `AppManifest.dependencies` restated the record without its range, and no install path consulted it. An author, often an AI, who wrote either got nothing enforced and no error. Measured at `origin/main``6390fe52ce`: zero readers and zero writers of either key outside `packages/spec` (`packages/`, `ui/`, `examples/`, `apps/`), and none in HotCRM at `9464673`, whose three modules declare their dependency in the record.
13
+
14
+
### FROM → TO
15
+
16
+
| removed | from | what to write instead |
17
+
| --- | --- | --- |
18
+
|`optional` on a `PackageDependency` entry (`PackageManifestSchema.dependencies[]`) |`@objectstack/spec/marketplace`| delete the key. An entry carries `packageId` and `versionRange` only. Declare a dependency in `manifest.dependencies` only when the package cannot install without it. |
19
+
|`dependencies` on `AppManifest`|`@objectstack/spec/system`| delete the key. Declare each dependency with its range in the package's `manifest.dependencies`, for example `{ 'com.acme.core': '^1.0.0' }`. |
20
+
21
+
**The one-line fix: delete the key.** A TypeScript author fails `tsc` first (each key is typed as the retired mark), and a parse of `PackageDependencySchema`, `PackageManifestSchema` or `AppManifestSchema` refuses the key with its prescription, the old default (`false`, `[]`) included. The parsed output no longer carries either key, so `PackageDependencyParsed` loses `optional` and `AppManifestParsed` loses `dependencies`.
22
+
23
+
⚠️ Runtime behaviour is deliberately **unchanged**: nothing ever read either key, so every install answers exactly as before. The removal retracts a false claim, not a capability. **No deprecation window** (startup-stage posture: retirements take effect immediately).
24
+
25
+
⚠️ **The out-of-repo consumer population beyond HotCRM is NOT MEASURED.**`@objectstack/spec` is published, and the control plane's reading of `sys_package_version.manifest_json` is not readable from this repository.
26
+
27
+
The ADR-0087 D3 semantic entry `dependency-unread-spellings-retired` carries the judgement, and both keys are registered in `RETIRED_KEYS_BY_MAJOR[18]`. There is no D2 conversion: neither shape is a stack collection member, so a conversion would have no source to rewrite.
fix(fields,plugin-grid): a grid's `date` column reads in one absolute face when its field declares no `format`
7
+
8
+
Clause-②: no
9
+
Metadata-protocol: none
10
+
11
+
A list grid's `date` column used to mix three faces in one column, for example "Yesterday · 3 days ago · Sep 25 · Sep 20, 2025". The cell's default face was `'relative'`, which writes a relative phrase within ±7 days of today and an absolute date outside that window. A sorted column did not look sorted, and "Yesterday" in an expiry column read as a status.
12
+
13
+
-**Default change for an undeclared `format` (visible from metadata).** In a grid column, a `date` field that declares no `format` now renders the locale's short absolute date: `Oct 25`, with the year added when the date is not in the current year. Every row of the column uses this one face. The group headings of a grid grouped by that field change with it, so a heading still reads like the cell under it.
14
+
-**The relative phrase moves to the tooltip.** It is shown on hover ("Yesterday", "In 2 days", or "Overdue 3d" on a declared `dueLike` field). A date outside the ±7-day window has no relative phrase, so it keeps its ISO `title` as before. A past declared deadline is still red.
15
+
-**A declared `format` still wins.** A field that declares `format: 'relative'` keeps the relative face in the grid. This is how an author who wants relative wording in a list keeps it.
16
+
-**Other surfaces are unchanged.** The record detail body, the header highlights strip, kanban and gallery cards, the tree, a record page's related list and the grid's record panel render `date` cells as before. `DateCellRenderer` paints the absolute default only when its host passes the new `inGridColumn` prop, and `ObjectGrid` passes it on every column it builds. On mobile, a grid card draws its title through the column's cell, so a `date` title reads absolute there too; the card's other date slots were already absolute. `datetime` cells are not affected, because their default face was already absolute (`compact`).
17
+
18
+
What to do: nothing. To keep the relative wording in a grid column, declare `format: 'relative'` on the field.
fix(app-shell): a record locked by an approval is shown locked to a reader outside that approval
6
+
7
+
Clause-②: no
8
+
Metadata-protocol: none
9
+
10
+
A member who can read a record but is not on its approval (not an approver, not the submitter, no override) was offered inline edit while a `lockRecord: true` approval held the record, and the save was then refused with `409 RECORD_LOCKED`. The approvals list serves a request only to its participants, so the record page read that member's empty list as "no approval is running".
11
+
12
+
When the list finds no pending request, the record page now also reads the record's own lock, `client.approvals.getRecordLock(object, recordId)`, which the server answers to every caller who can read the record with `{ locked }` and nothing else. A locked answer shows the record locked: Edit is disabled, inline edit is not offered, and the "Locked for approval" band is drawn without a recall button, because a reader who cannot see the request did not submit it. The reader still sees nothing of the request itself (approvers, comments, attachments).
13
+
14
+
Unchanged: a pending request the viewer can see still decides with its own node's `lockRecord`, and a composition without the approvals service (the lock read answers `501`) still opens an answered empty record editable.
Copy file name to clipboardExpand all lines: content/docs/references/marketplace/package-version.mdx
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -64,7 +64,7 @@ Package dependency declaration
64
64
| :--- | :--- | :--- | :--- |
65
65
|**packageId**|`string`| ✅ | Manifest ID of the dependency |
66
66
|**versionRange**|`string`| ✅ | Semver version range (e.g. ^1.0.0) |
67
-
|**optional**|`boolean`| optional (default: `false`) | Whether this dependency is optional|
67
+
|**optional**|`never`| optional |[REMOVED]`PackageDependency.optional` was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove, ADR-0141 D8) — no install path ever honoured it: the pre-install check reads the package's `manifest.dependencies` record, which has no optional arm, and treats every entry as required. Delete the key. Declare a dependency in `manifest.dependencies` (`{ 'com.acme.core': '^1.0.0' }`) only when the package cannot install without it.|
68
68
69
69
70
70
---
@@ -83,7 +83,7 @@ Package manifest snapshot embedded in a package version
83
83
|**description**|`string`| optional | Short description |
|**packageId**|`string`| ✅ | Manifest ID of the dependency |
99
99
|**versionRange**|`string`| ✅ | Semver version range (e.g. ^1.0.0) |
100
-
|**optional**|`boolean`| optional (default: `false`) | Whether this dependency is optional|
100
+
|**optional**|`never`| optional |[REMOVED]`PackageDependency.optional` was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove, ADR-0141 D8) — no install path ever honoured it: the pre-install check reads the package's `manifest.dependencies` record, which has no optional arm, and treats every entry as required. Delete the key. Declare a dependency in `manifest.dependencies` (`{ 'com.acme.core': '^1.0.0' }`) only when the package cannot install without it.|
|**dependencies**|`never`| optional |[REMOVED]`AppManifest.dependencies` was removed in @objectstack/spec 18 (ADR-0049 enforce-or-remove, ADR-0141 D8) — nothing read it: it listed bare ids with no version range, and no install path consulted it. Delete the key. Declare each dependency with its range in the package's `manifest.dependencies` (`{ 'com.acme.core': '^1.0.0' }`), which the pre-install check enforces.|
0 commit comments