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 886ad2c
Browse filesBrowse the repository at this point in the historyBrowse files
feat(cli)!: `objectstack validate` and `objectstack build` refuse a field whose `picklist` names no picklist the stack declares, and lint R8 counts `picklist` as an options source (#20825)
7
+
8
+
Clause-②: no (narrowing — `objectstack validate` / `objectstack build` newly refuse a field `picklist` that names no picklist the stack declares; the R8 change removes a false-positive warning and widens no accept set of its own)
9
+
10
+
<!-- adr-0087: not-required (no-migration-prescription) Nothing authorable changes spelling or type: `packages/spec` is untouched, and `Field.picklist` stays the snake_case name it was. What changes is that two authoring commands now refuse one authored shape, a field whose `picklist` names no picklist in the stack. `objectstack migrate meta` could not rewrite that shape even in principle, because which list the author meant is not in the metadata. Nothing here judges a stored row. -->
11
+
12
+
**BREAKING** — an accept-set narrowing on two authoring commands, shipped as
13
+
`minor` under the launch-window convention. A stack with a select field whose
14
+
`picklist` names no picklist the stack declares — `picklist: 'industy'` beside a
15
+
`picklists: [{ name: 'industry', … }]` — used to pass `objectstack validate` and
16
+
`objectstack build` (which wrote the artifact). Both now exit 1 and name the field
17
+
and the list it names (`picklist-reference-unknown`).
18
+
**One-line fix:** correct `picklist` to a list the stack declares, or declare the
19
+
list it names (`picklists: [{ name, label, options }]`, or a `*.picklist.ts` file the
20
+
stack imports).
21
+
22
+
**Which references are judged.** The ones the load path registers: the top-level
23
+
`objects` and `objectExtensions` of a one-package stack, or each `packages[]`
24
+
entry's own. A reference resolves against every picklist the stack declares,
25
+
including one a sibling package in the same artifact owns.
26
+
27
+
**A list from a package outside the stack is reported, not refused.** When the
28
+
package declaring the field lists a `manifest.dependencies` entry the stack does not
29
+
carry, the list may live there, and these commands cannot read it. That reference is
30
+
an `info` notice (`picklist-reference-unverified`) in `warnings` and on the console,
31
+
naming the field, the list and the dependencies — never a failure, not even under
32
+
`--strict`.
33
+
34
+
**Lint R8 (`field/select-missing-options`)** no longer reports a select, multiselect
35
+
or radio field that names a `picklist`: the picklist is its options source. The
36
+
warning it used to give pointed at `options`, which a field naming a `picklist`
37
+
cannot add — the field schema refuses the two together. A select with neither still
38
+
warns, and its fix now names both sources as alternatives.
fix(approvals): a snapshot field the reader is served masked on the data plane is no longer served as stored (#20964)
6
+
7
+
Clause-②: yes (widening)
8
+
9
+
The approval payload snapshot is redacted at serve time by the security service's read projection, `getReadableFields`. That projection counts a field whose `maskingRule` applies to the reader as readable, because the data plane serves the column with its value replaced. So the snapshot kept such a field and served it as captured at submission. The redaction now also reads the security contract's `getQueryableFields`, which differs from the read projection by exactly the fields the reader is served masked, and drops those fields, with their derived labels, on both read doors: the approvals inbox reads and the generic data door on the request object. A reader for whom the masking rule is lifted still sees the stored value. The full snapshot stays at rest.
10
+
11
+
The field is dropped rather than masked. The contract names which fields are masked for a reader, not the masked value, and reproducing the mask in this plugin would be a second copy of the masking rule.
12
+
13
+
The one public-surface addition is an optional `getQueryableFields(object, context)` member on the field-visibility source that `ApprovalServiceOptions.fieldVisibility` and `ApprovalService.attachFieldVisibility` accept.
14
+
15
+
A host that constructs `ApprovalService` itself and passes its own `fieldVisibility` source: that source must now also answer `getQueryableFields` (delegate it to the `security` service). A source without it cannot say which readable fields are masked for the reader, so the redaction fails closed and serves no snapshot field. The approvals plugin's own wiring already forwards it.
"examples/app-showcase/src/ui/dashboards/ops-dashboard.dashboard.ts#dateRange (dateRange + global filter levers)",
639
-
"examples/app-showcase/src/data/seed/index.ts#signed_on (prospects carry no signed_on — the deliberate NULL population)"
639
+
"examples/app-showcase/src/data/seed/index.ts#accounts (prospects carry no signed_on — the deliberate NULL population; re-pointed #18101: signed_on is a key written INLINE in each seed row, which the shared resolver does not read as a declaration, and accounts is the seed constant that declares those rows)"
"examples/app-showcase/src/data/seed/index.ts#sales_region (sales_region + signed_on seeding, prospects unset by design)",
752
+
"examples/app-showcase/src/data/seed/index.ts#accounts (sales_region + signed_on seeding, prospects unset by design — re-pointed #18101: both keys are written INLINE in each seed row, which the shared resolver does not read as a declaration, and accounts is the seed constant that declares those rows)",
"objectui packages/plugin-dashboard/src/DashboardFilterBar.tsx (isDirty→Reset affordance RotateCcw, data-testid dashboard-filter-bar; SelectFilter {value,label} options + server GROUP BY via queryDataset, #2578 item 5)",
"packages/rest/src/rest-route-ledger.ts#REST_ROUTE_LEDGER (GET /api/v1/security/my-delegable-scope — security.describeDelegableScope, ADR-0090 D12 / ADR-0105 D8, self-scoped read half of the delegated-admin gate — re-pointed #18104: describeDelegableScope is a CLIENT METHOD name, carried in this file only inside a string value)",
360
360
"packages/spec/src/contracts/security-service.ts#DelegableScope (DelegableScope: isTenantAdmin, scopes, placeableBusinessUnitIds, assignablePositions — no invitation-role field, which is why the scope read cannot be the invite picker's allowlist)",
"packages/spec/src/kernel/public-auth-features.ts#PUBLIC_AUTH_FEATURES.organization (organization feature gates sys_invitation invite/cancel/resend actions — re-pointed #18101: sys_invitation is carried in this file only as the HEAD of dotted action names, never as a complete quoted token, and the organization entry of PUBLIC_AUTH_FEATURES is the declaration that holds them)"
362
362
],
363
363
"history": [
364
364
{ "revision": 1, "date": "2026-08-07", "change": "new item: invitation scope gates and lifecycle, pinned to the delegated-admin-invite dogfood test", "ref": "claude/platform-test-checklist-ocwugl" },
"packages/plugins/plugin-security/src/security-plugin.ts#SecurityPlugin (§A5 #3023 EXEMPTION: __referentialFieldClear owner_id-null cascade rides a server-derived context, the owner-anchor guard must not veto it — re-pointed #18104: __referentialFieldClear is a CONTEXT KEY this file only reads off opCtx.context, a member access; the guard that honours it is in the SecurityPlugin class this file declares) + security-plugin.test.ts '[#3023] … engine referential FK clear … is exempt'",
482
-
"packages/spec/src/kernel/public-auth-features.ts#sys_user (admin flag gates the sys_user lifecycle actions; SCIM forces it on — ADR-0134)",
482
+
"packages/spec/src/kernel/public-auth-features.ts#PUBLIC_AUTH_FEATURES.admin (admin flag gates the sys_user lifecycle actions; SCIM forces it on — ADR-0134; re-pointed #18101: sys_user is carried in this file only as the HEAD of dotted action names, never as a complete quoted token, and the admin entry of PUBLIC_AUTH_FEATURES is the declaration that holds the lifecycle ones)",
"packages/plugins/plugin-auth/src/anonymous-session-refusal.ts#ANONYMOUS_SESSION_REFUSAL_STATUS (since #17881 an anonymous or revoked /get-session answers 401 with the ADR-0112 refusal envelope, code UNAUTHENTICATED derived from that status; a live session still answers 200 with { user, session }. Both legs are driven end to end in packages/plugins/plugin-auth/src/anonymous-session-refusal.test.ts, and the body-not-status discipline for a revoke is kept in packages/plugins/plugin-auth/src/session-of-record.test.ts)"
"packages/connectors/connector-mcp/src/mcp-provider.ts (#3055 policy: default deny, strict-equality allowlist, both messages)",
259
-
"examples/app-showcase/objectstack.config.ts#declarativeStdio (declarativeStdio: ['node'] opt-in) + src/system/connectors/index.ts (the 'remove that option and boot fails loudly — try it' fixture note)"
259
+
"examples/app-showcase/objectstack.config.ts#plugins (declarativeStdio: ['node'] opt-in on the ConnectorMcpPlugin entry — re-pointed #18101: declarativeStdio is a key written INLINE in that constructor call, which the shared resolver does not read as a declaration, and the config itself is an anonymous default export, so plugins is the nearest enclosing key it does read) + src/system/connectors/index.ts (the 'remove that option and boot fails loudly — try it' fixture note)"
"packages/spec/src/data/datasource.zod.ts#checkOnBoot (validation policy schema; checkOnBoot default true, the checkonboot/validateonboot alias rows are REJECTION-path did-you-means, not folds — enforced since #13149, so declared = enforced here now)",
1409
-
"examples/app-showcase/src/system/datasources/showcase-external.datasource.ts#onMismatch (stock policy onMismatch:'warn' — deliberate, per its own comment)",
1409
+
"examples/app-showcase/src/system/datasources/showcase-external.datasource.ts#ShowcaseExternalDatasource (stock policy onMismatch:'warn' — deliberate, per its own comment; re-pointed #18101: onMismatch is a key written INLINE in the external validation literal, which the shared resolver does not read as a declaration, and ShowcaseExternalDatasource is the datasource constant that declares it)",
"examples/app-showcase/src/ui/actions/index.ts#ActionParamGalleryAction (maxSize declarations on its image and file params — re-pointed #18101: maxSize is a key written INLINE in each param literal, which the shared resolver does not read as a declaration, and ActionParamGalleryAction is the action constant that declares those params)"
"examples/app-showcase/src/data/objects/business-unit.object.ts#BusinessUnit.parent + category.object.ts (parent allowCreate:true — re-pointed #18101: allowCreate is a key written INLINE in the lookup options, which the shared resolver does not read as a declaration, and the parent field of the BusinessUnit object is the declaration that carries it)",
0 commit comments