Repository navigation
Commit 67b669e
fix(runtime,core,platform-objects): the catalog activation door switches an environment-authored position or permission set, and keeps ADR-0112's code and status together (#22811)
Part of #15204
Clause-②: yes (widening)
Stage 2d of #15204 (claim amendment 6106681412): the security catalog's
activation door switches a position or permission set the environment
authored, and a ledger write it refuses answers with the status its code
means (ADR-0112). This PR does not close the card: the other stages stay
open.
## The defect (F5 of objectui#12089 round 2), reproduced first
On `origin/main` `9f5eca5`, the dogfood case added here failed before
any fix:
```
AssertionError: {"success":false,"error":{"code":"VALIDATION_FAILED","message":"Package is required","httpStatus":503}}: expected 503 to be 200
```
- `handleCatalogActivationWrite` wrote `packageId: entry.packageId ??
''`. An item saved through `PUT /meta/position/:name` or `PUT
/meta/permission/:name` has no `_packageId`, so the row carried `''`.
- `sys_metadata_activation.package_id` was `Field.text({ required: true
})`, so the engine refused the row: `VALIDATION_FAILED`, with no status
of its own.
- The door's `catch` kept the thrown `code` but defaulted the status to
`503`. The code and the status disagreed.
## Who writes and reads `package_id` (measured on `9f5eca5`)
| Seam | Uses `package_id` for |
|:--|:--|
| catalog door (`runtime/src/domains/catalog-activation.ts`) | writes
`entry.packageId ?? ''` (the producer of F5) |
| action door (`runtime/src/domains/actions.ts:223`) | writes the
action's `_packageId`, else `''` |
| flow toggle (`service-automation/src/engine.ts`) | writes the loader's
package; a flow no package ships is refused before the write |
| `ObjectStoreMetadataActivationStore.list()` (core) | maps it onto
`packageId`; the flow and action hydrators (`hydrateFlowActivations`,
`ActionActivationProjection.hydrate`) read only `name` and `active` |
| `readDisabledCatalogNames`
(`core/src/security/resolve-authz-context.ts`) | not read: type, name,
active |
| `catalog-set-in-effect.ts`, `last-admin-guard.ts` (plugin-auth),
`sharing-rule-service.ts` (plugin-sharing) | not read |
| objectui console at `main` `a194b4e` | `git grep package_id` over the
ledger readers: no hit |
The row identity is `(metadata_type, name)` (the `'global'` unique
index). No reader decides anything from `package_id`.
## The fix, and why option (i)
**(i) `package_id` becomes optional, and "no package" has exactly one
spelling.**
- `sys_metadata_activation.package_id`: `required` is dropped. The
field's description now says it is empty when no package ships the item.
The column was already nullable on disk: under ADR-0113 `required` is a
write-time check, and NOT NULL comes only from `storage.notNull`. So no
DDL and no migration.
- `MetadataActivationRow.packageId` is optional (`@objectstack/core`).
The store writes `null` when it is absent. It **refuses `packageId:
''`** with a `TypeError`, before any read or write. `list()` returns
`packageId` only when the row names a package. So a package-less row has
one spelling, and no reader can take an environment item for a package
named `''`.
- The door passes `entry.packageId` as it is. The `?? ''` is gone.
- The door's `catch` answers through `deps.errorFromThrown(err, 503)`.
That is the dispatcher's one thrown-error rule
(`resolveThrownHttpError`). A record validation refusal becomes `400
VALIDATION_FAILED`. A hook refusal keeps its own pair (`403
PERMISSION_DENIED`). An error with no status of its own becomes `503
SERVICE_UNAVAILABLE`.
- `FlowActivationRow.packageId` is optional to match (type only).
Without that, `ObjectStoreFlowActivationStore implements
FlowActivationStore` no longer type-checks. This was reverse-verified:
putting back `packageId: string` gives 10 × TS2345 in
service-automation.
**On the four axes:**
- **Real business need.** The environment-authored items are real:
ruling #22621 → A covers them, and the stage 2c door already resolves
them. Measured readers: none reads `package_id`. A marker string (ii)
would serve no reader.
- **Long-term soundness.** (i) records the truth in the column: there is
no package. (ii) would put a sentinel into a column described as a
package id. Every future reader that compares it with a package would
then have to know the dialect. That is a second spelling of "no package"
next to `null`.
- **Preventing AI mistakes.** (i) closes the hole on the producer side.
The `?? ''` is gone, and the store loudly refuses the `''` placeholder a
caller would reach for. (ii) would make the marker a magic value that an
AI-written reader could easily treat as a real package.
- **No scope spread.** (i) is a smaller diff: one field flag, one
optional type, one refusal. No new vocabulary.
## Pins
- `catalog-activation-door.test.ts` (runtime, unit). New tests:
- an environment-authored position and set, in both directions, each
writing exactly `{ metadata_type, name, package_id: null, active }`;
- an anti-vacuity check that the catalog resolves both with `packageId`
undefined, beside a packaged control that names `crm`;
- the resolver honours the row for the position and for the set;
- a record validation refusal is `400` with `VALIDATION_FAILED`, and
nothing is written.
- `metadata-activation-store.test.ts` (core). Inserting and updating
with no package write `package_id: null`. `''` is refused before any
engine call. `list()` reads a package-less row back with `packageId`
absent, beside a packaged row.
- `catalog-activation-door.dogfood.test.ts` (real showcase boot). Both
items are saved through `/meta`. Anti-vacuity: the booted catalog reader
resolves both with no `packageId`. A holder is assigned the position.
The test then checks the door, the ledger row (`package_id` null), and
`resolveUserAuthzGrants` dropping and restoring the grants, for the
position and for the set.
## Verification
- **Reproduction.** On base `9f5eca5`, the dogfood case was red (503
above). With the fix it is green: `Tests 8 passed (8)`.
- **Ablation**, with `ablation-replace.mjs` and
`ablation-dist-preflight.mjs`. Only `required: true` was put back on
`package_id`, and `@objectstack/platform-objects` was rebuilt (the
marker in `dist/` was confirmed). Result: the dogfood case went red as
`{"code":"VALIDATION_FAILED",...,"httpStatus":400,"details":{"fields":[{"field":"package_id","code":"required",...}]}}`.
That is now 400, not 503, so the code-and-status fix is shown on a real
engine. Restore: the blob is byte-identical to HEAD, the build was
redone, and the marker is absent from all 66 dist files.
- **Full suites** of every edited package, on the pre-merge head
`db73adc341`:
- core: 2353 passed;
- platform-objects: 1082;
- service-automation: 2368;
- runtime: 5038 passed, 19 skipped.
- **Typecheck** of core, objectql, service-automation, runtime and
platform-objects: green.
- **Dogfood, in full,** on the merged head `bf3f89075e`: 245 files
passed and 1 skipped; 1978 tests passed and 9 skipped.
- **Gates.** `dispatch-gates --commands` derived 100 families, and all
100 exited 0 on `bf3f89075e`. `--ran` reconciles them: "100 derived
famil(ies) accounted for — 100 run, 0 NOT-MEASURED (a DERIVED zero)".
- **eslint** over the 7 changed TypeScript files: clean. This is a
narrowed run, not the repo-wide lint, which CI owns.
## Acceptance notes
- **The action activation door is not changed.** For an action no
package ships, it still passes `''`. That write was refused before (503
`VALIDATION_FAILED`, from the engine) and is still refused, with nothing
written. Its answer is now 503 `SERVICE_UNAVAILABLE`, from the store's
`TypeError`. Whether an environment-authored action gets a switch is a
product question, not decided here. Its `catch` has the same shape as
the one this PR fixes, but `actions.ts` is outside this stage's declared
files.
- The door's authority (`manage_metadata`, operator-only under a wall)
is unchanged (#22621 → A). The anchors' 400, the unknown-name 404 and
the last-admin 403 are unchanged.
- objectui#12089's round-2 report and the seat note on objectui#7611
could not be read from this session: the objectui API is not attached
here. F5 was taken from the restatement in claim amendment 6106681412
and reproduced independently, as above.
---
_Generated by [Claude
Code](https://claude.ai/code/session_014ifLBrKenJNWpsLxhLfhUg)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent f313fbc commit 67b669e
9 files changed
Lines changed: 344 additions & 33 deletions
File tree
- .changeset
- content/docs/permissions
- packages
- core/src/utils
- platform-objects/src/system
- qa/dogfood/test
- runtime/src/domains
- services/service-automation/src
Lines changed: 17 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
414 | 414 | | |
415 | 415 | | |
416 | 416 | | |
417 | | - | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
418 | 425 | | |
419 | 426 | | |
420 | 427 | | |
| |||
Lines changed: 56 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
296 | 296 | | |
297 | 297 | | |
298 | 298 | | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
299 | 355 | | |
300 | 356 | | |
301 | 357 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
89 | | - | |
| 89 | + | |
90 | 90 | | |
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
94 | 94 | | |
95 | 95 | | |
96 | | - | |
| 96 | + | |
97 | 97 | | |
98 | | - | |
99 | | - | |
100 | | - | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
101 | 108 | | |
102 | 109 | | |
103 | 110 | | |
| |||
190 | 197 | | |
191 | 198 | | |
192 | 199 | | |
193 | | - | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
194 | 203 | | |
195 | 204 | | |
196 | 205 | | |
| |||
216 | 225 | | |
217 | 226 | | |
218 | 227 | | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
219 | 235 | | |
220 | 236 | | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
221 | 244 | | |
222 | 245 | | |
223 | 246 | | |
| |||
227 | 250 | | |
228 | 251 | | |
229 | 252 | | |
230 | | - | |
| 253 | + | |
231 | 254 | | |
232 | 255 | | |
233 | 256 | | |
| |||
238 | 261 | | |
239 | 262 | | |
240 | 263 | | |
241 | | - | |
| 264 | + | |
242 | 265 | | |
243 | 266 | | |
244 | 267 | | |
| |||
Lines changed: 20 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
58 | 64 | | |
59 | 65 | | |
60 | 66 | | |
| |||
65 | 71 | | |
66 | 72 | | |
67 | 73 | | |
68 | | - | |
| 74 | + | |
69 | 75 | | |
70 | 76 | | |
71 | 77 | | |
| |||
124 | 130 | | |
125 | 131 | | |
126 | 132 | | |
127 | | - | |
| 133 | + | |
128 | 134 | | |
129 | 135 | | |
130 | 136 | | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
131 | 146 | | |
132 | 147 | | |
133 | | - | |
134 | 148 | | |
135 | | - | |
| 149 | + | |
| 150 | + | |
136 | 151 | | |
137 | 152 | | |
138 | 153 | | |
139 | 154 | | |
140 | 155 | | |
141 | 156 | | |
142 | | - | |
| 157 | + | |
143 | 158 | | |
144 | 159 | | |
145 | 160 | | |
| |||
0 commit comments