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 9ebb8ae
Browse filesBrowse the repository at this point in the historyBrowse files
fix(objectql)!: the engine refuses evaluating an internal field for every caller but a system context
The `internal: true` evaluate refusal (filter, sort, group key, aggregation
operand, aggregation filter, and the same positions inside expand) is now
defined once, in the engine, and answers the data door's 400 INVALID_FIELD
with the door's sentence.
- objectql: find, findOne, count and aggregate apply it to every caller but
a system context; ObjectQL#assertNoInternalFieldEvaluated answers it for
any caller, with a per-hop hook a door hands its own exposure question.
- spec: QueryEvaluationGate, InternalFieldEvaluationQuery and
InternalFieldEvaluationOptions on the objectql slot contract.
- metadata-protocol: the data door asks the engine's refusal instead of
keeping its own copy; on an engine without the gate it still asks its
relation-target exposure question at every hop.
- runtime: the HTTP MCP data bridge asks the engine's refusal directly.
- mcp: the stdio data bridge's copy is retired in favour of the engine's.
Claude-Session: https://claude.ai/code/session_01FEkQ5nqYhjhRCqHrhVA59A
Co-Authored-By: Claude <noreply@anthropic.com>
fix(objectql)!: the engine refuses filtering, sorting, grouping and aggregating on an `internal: true` field for every caller but a system context
10
+
11
+
Clause-②: yes (narrowing)
12
+
Metadata-protocol: none
13
+
14
+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped, and nothing an author writes is refused: the `internal: true` flag is read as before. The narrowing is a runtime one, on reads made as a user: a query that evaluates a flagged field answers 400 INVALID_FIELD from the engine itself, with the data door's sentence. The published type change adds a member to the `objectql` slot contract and is satisfied by the one implementer, ObjectQL. There is nothing for `objectstack migrate meta` to rewrite and no tombstone; the remedy is the refusal's own sentence (use another field), or running the server's own read as a system context. -->
15
+
Skills-docs: updated here — `content/docs/permissions/system-context.mdx` (row 18e: the engine's read path) and `content/docs/api/error-catalog.mdx` (`INVALID_FIELD`) state who is refused and who is served; no file under `skills/**` teaches filtering on an `internal: true` field, so no skill line changes.
16
+
17
+
**BREAKING** for one shape only: server code that reads the engine as a user (not as a system context) and filters, sorts, groups or aggregates on a field declared `internal: true`.
18
+
19
+
- FROM: the engine ran such a read.
20
+
- TO: the engine refuses it with the data door's `400 INVALID_FIELD`, naming the object, the field and the position, before anything is read. A read made as a system context (`isSystem: true`) is served as before, and the flagged column is still never returned on a row.
21
+
22
+
What to do: run a server-side lookup that matches on a flagged field as a system context, or match on another field.
23
+
24
+
-**`@objectstack/objectql`**: the `internal: true` evaluate refusal is defined once, in the engine. `find`, `findOne`, `count` and `aggregate` apply it to every caller but a system context, in every position the data door judges: a filter (including a relation hop and a `$field` comparand), a sort, a group key, an aggregation operand and an aggregation's own filter, and the same positions inside `expand`. `ObjectQL#assertNoInternalFieldEvaluated` answers the same question for any caller, for a door that judges before it reads.
25
+
-**`@objectstack/spec`**: new types on the `objectql` slot contract: `QueryEvaluationGate`, `InternalFieldEvaluationQuery` and `InternalFieldEvaluationOptions`. `IObjectQLEngine` extends `QueryEvaluationGate`.
26
+
-**`@objectstack/metadata-protocol`**: the data door asks the engine's refusal instead of keeping its own copy. Its answers are unchanged.
27
+
-**`@objectstack/runtime`**: the HTTP MCP data bridge asks the engine's refusal directly. Its answers are the data door's.
28
+
-**`@objectstack/mcp`**: the stdio data bridge's own copy of the refusal is retired in favour of the engine's. Its answers are the data door's.
29
+
30
+
The level is patch rather than the minor a new spec type would take: the changeset gate refuses `minor` beside `Metadata-protocol: none`, and nothing an author writes is refused.
Copy file name to clipboardExpand all lines: content/docs/permissions/system-context.mdx
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -123,6 +123,7 @@ bugs — it is the protection or the side effect that silently does not happen.
123
123
| 18b | Settings namespace read scope not applied | service-settings | Get: a read of `sys_setting`, `sys_setting_audit` or `sys_platform_setting` across every namespace, whatever each namespace's `readPermission` names — the settings service's own reads of its stores take this path. Lose: the per-namespace read gate the generic data API applies to every other caller, the same rule the settings routes enforce |`settingsReadDoorMiddleware`|
124
124
| 18c |**Credential-column filter / sort refusal not applied** — a `where`, `orderBy`, per-aggregation `filter` or `expand` entry naming a `password` / `secret` column is evaluated | objectql | Get: the server's own match on a stored credential value, a lookup that never returns it. Every other caller, administrators included, is refused `400 INVALID_FIELD` for anything but a has-a-value check (ADR-0100). Lose: nothing a wire caller reaches — `isSystem` is never set on an inbound request |`rejectCredentialEvaluation`|
125
125
| 18d |**Stored-metadata type read gate not applied** — a read of `sys_metadata` / `sys_metadata_history` is not narrowed by the capability its row's type requires | objectql | Get: every row of the family on `find` / `findOne` / `count` / `aggregate` — the metadata loader, the `/meta` doors' own store reads, boot (a read with no execution context is not narrowed either). Lose: the narrowing that serves a stored row of a capability-gated type (`datasource`, `external_catalog`) only to holders of the capability the `/meta` type gate names for it |`storedMetadataTypeReadFilter`|
126
+
| 18e |**`internal: true` evaluate refusal not applied on the engine's read path** — a filter, sort, group key, aggregation operand, per-aggregation `filter`, nested-relation condition or `expand` entry naming a field declared `internal: true` is evaluated | objectql | Get: the server's own match on a flagged column (the API-key, share-link, SCIM-credential and approval-token lookups, the better-auth adapter), which still never returns the value. Every other caller of `find` / `findOne` / `count` / `aggregate`, administrators included, is refused `400 INVALID_FIELD`; the data door asks the same judgement for every caller, a system context included. Lose: nothing a wire caller reaches — `isSystem` is never set on an inbound request |`rejectInternalFieldEvaluation`|
126
127
127
128
### 2. Write pipeline and data integrity
128
129
@@ -544,6 +545,7 @@ and CI refuses one that differs from what the tree produces.
0 commit comments