Skip to content

Commit aeba432

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-22764-rest-write-fault-withheld
2 parents 1b89f1c + a6f3a13 commit aeba432

67 files changed

Lines changed: 1823 additions & 2160 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/plugin-security': minor
3+
---
4+
5+
feat(plugin-security): the boot no longer writes `sys_position` rows — the built-in and declared position seeders and the `everyone` baseline junction writer are deleted
6+
7+
Clause-②: no
8+
9+
`SecurityPlugin` no longer seeds a `sys_position` row for the six built-in positions (`platform_admin`, `org_owner`, `org_admin`, `org_member`, `everyone`, `guest`) or for a position a stack declares, and no longer writes a `sys_position_permission_set` row binding the baseline sets to `everyone`. Positions are served by the security catalog (ADR-0131 D2): the built-ins are this plugin's declarations, a stack's positions are registry items, and `everyone`'s baseline — the app's default set and `member_default`, minus any set the high-privilege check refuses — is that anchor's declared `permissionSets`. `GET /api/v1/meta/position` lists all of them, and assigning a declared position needs no row.
10+
11+
No authorization decision changes: since the resolver reads position definitions, no grant reads a `sys_position` or `sys_position_permission_set` row, and a fresh database and an existing one grant every principal the same. On a database that was already seeded, the existing rows stay as they are; they are no longer refreshed at boot.
12+
13+
FROM → TO:
14+
15+
- FROM reading the built-in or declared positions from `sys_position` → TO the security catalog read (`createSecurityCatalogReader`) or `GET /api/v1/meta/position`.
16+
- FROM the `everyone` → baseline `sys_position_permission_set` rows → TO the `everyone` definition's `permissionSets`.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
"@objectstack/lint": patch
3+
---
4+
5+
fix(lint): the flow-credential, AI skill, flow-filter-token, managed-object API method, organization-axis business-unit grant, retired permission residue, seed and semantic-role provenance findings print one verdict line, `os explain <rule-id>` carries their reasoning, and `field-no-consumers` stops listing every object that shares a field name
6+
7+
Clause-②: no
8+
9+
- **Shorter verdicts.** Each finding of these 10 rule ids now prints a `message` of one verdict sentence. Every finding the rules' own test suites fire is at most 194 characters, and the runtime publish gate's suite at most 181; before, the longest of each ran from 222 to 441 characters. The ids:
10+
- flows: `flow-credential-literal` (a literal credential in an `http` node's headers or url query, or in a `connectorConfig.input`) and `flow-filter-token-unknown` (a `{NAME()}` filter token neither the flow template dialect nor the filter placeholders define);
11+
- AI agents and skills: `ai-skill-surface-mismatch` and `ai-skill-tool-unresolved`;
12+
- managed objects: `object/managed-api-method-unaffordable`;
13+
- sharing rules: `org-axis-cross-org-bu-grant` (its sibling `org-axis-permission-inheritance` already printed one sentence of at most 174 characters and is unchanged);
14+
- permission sets: `permission-retired-lifecycle-residue`;
15+
- seed datasets: `seed-insert-mode-duplicates-on-replay` and `seed-value-outside-state-machine`;
16+
- semantic roles: `semantic-role-field-unprovisioned`.
17+
18+
A verdict no longer repeats what the finding's `where` already names: the skill (`ai-skill-tool-unresolved` now opens on the tool), the managed object, the sharing rule's object and the semantic-role object. `flow-credential-literal` keeps a short route (`move it to a connector's \`auth.credentialRef\``, or `drop it` for a connector input) because `os lint` prints the fix only under `--fix`; the route by auth variant stays the fix line's. `seed-value-outside-state-machine` names the first three declared states, then `(and N more)`. `object/managed-api-method-unaffordable` no longer appends the spec's `describeManagedApiMethodConflicts` sentence; it names the refused verbs, the `managedBy` bucket and the affordances they need. The `fix` (the CLI's `fix:` line, the runtime issue's `hint`), every rule id, severity and `path`, and what each rule accepts or refuses are unchanged. A tool that matched the old message text should match on `rule` and `path` instead.
19+
- **`field-no-consumers`** names the first other object that declares a field of the same name and counts the rest (`a consumer of the same name on "showcase_account" (and 9 more) does not count`), where it listed every one. On `os validate` of the showcase app its longest finding drops from 341 characters to 199, and that 341-character one, which named ten objects, now prints 143. Its `inert` and `carrier-only` verdicts and its explanation are otherwise unchanged.
20+
- **`os explain <rule-id>` takes these 10 ids**, for example `os explain flow-credential-literal`. It prints the reasoning the verdicts no longer carry: which flow positions are served to every flow reader and where a credential belongs, by auth variant; how an agent's and a skill's surfaces must match, and why an unresolved skill name is skipped; how a skill's tool name resolves and when an action materialises as a tool; the two vocabularies a flow filter token resolves in, and why a miss stops the node; which affordance each write verb needs and what the registry does with a refused one; why business-unit trees stay inside one organization and which recipients may share a platform-global object; why the retired `allowRestore` / `allowPurge` default is accepted in silence and which channels miss it; why seeds must be safe to replay and which states a seeded value may name; and why an injected column on an external object is blank. The `rule:` line under each of these findings now ends with `` — `os explain <rule-id>` for … ``. The no-argument listing and its `--json` `rules` array list the 10 ids, and so does the unknown-id error's `Rules with an explanation:` line. `RULE_EXPLANATIONS` in `@objectstack/lint` gains the 10 entries.
21+
- **Where the new text prints.** On the CLI, all 11 ids: `os validate`, `os build` (and `os compile`, which `os dev` runs when it compiles at startup, and on each watch recompile under `--verbose`), `os lint` (as `where: message`) and the scaffold check `os init` runs print the new `message` on the text face, and `os validate --json` and `os build --json` carry it in their `errors` and author-time `issues`. `os verify` prints the error-severity ids' new text in its refusal list (`ai-skill-surface-mismatch`, `flow-filter-token-unknown`, `object/managed-api-method-unaffordable`, `org-axis-cross-org-bu-grant`) and counts the warnings without printing them. At the runtime publish gate (Studio, REST `/meta`, MCP): a `flow` write's `flow-filter-token-unknown` and an `object` write's `object/managed-api-method-unaffordable` change the 422 issue's `message` and the refusal log line under `OS_ALLOW_UNLINTED_METADATA_WRITES`; a `flow` write's `flow-credential-literal` and a `permission` write's `permission-retired-lifecycle-residue` change the 2xx `advisories` entry and the `[Protocol] authoring advisory` log line. Each issue's `hint` is unchanged.
22+
- **Never at the runtime gate:** `org-axis-cross-org-bu-grant`, the two seed ids, `semantic-role-field-unprovisioned` and `field-no-consumers` (CLI-only rules), and the two AI ids, whose rules run at that door only for a `flow` write, whose snapshot carries no agents or skills of their own.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/verify': minor
3+
---
4+
5+
fix(verify)!: `hooks.run(object, 'delete', …)` resolves with what the engine's `delete` answers, not with a row
6+
7+
Clause-②: no (narrowing)
8+
9+
<!-- adr-0087: not-required (runtime-interface-only packages/verify/src/handle.ts#VerifyHandle) One TypeScript-only declaration moves, and it is not metadata: the `hooks.run` member of the published `VerifyHandle` interface, whose `delete` answer narrows from a row to the engine's own declared delete answer. No spec key, authorable spelling, Zod schema, export name or stored shape moves, and no runtime behaviour changes, so `objectstack migrate meta` has nothing to rewrite. The party affected is a TypeScript author who read a field of a delete's answer: the compiler names that read at their own call site, and the body says what to assert instead. The other categories are closed on facts: the package publishes (not `unpublished`); no ADR-0087 id covers a test handle's return type, and this diff adds none (not `registered` / `already-registered`); and the member was typed with the named `EngineRow` at the merge base, which predicate 4 reads as concrete, not erased (not `type-surface-only`). -->
10+
11+
**BREAKING** for TypeScript code that reads the answer of a `hooks.run` delete. It ships as `minor` under the launch-window convention for breaking changes. No runtime behaviour changes.
12+
13+
`hooks.run(object, 'delete', { id }, opts)`, for a person (`{ as }`) and for the system (`{ system: true }`), was declared to resolve with a row (`Promise<EngineRow>`). The engine's `delete` answers `true` for the row it removed. So a test that read a field of the answer type-checked, and then read `undefined`.
14+
15+
**What `hooks.run` declares now.** Both overloads are generic over the operation.
16+
17+
- An `insert` or an `update` resolves with the row, as before.
18+
- A `delete` resolves with the engine's own declared delete answer, `boolean | number`. It is read off `ObjectQL['delete']` rather than re-declared, and a `hooks.run` delete, which is always by id, answers `true`.
19+
- A call whose operation is the union `'insert' | 'update' | 'delete'` still type-checks, and resolves with the union of the answers.
20+
21+
**If your code read a field of a delete's answer.** That read was always `undefined` at run time, and it is now a compile error. Assert the answer itself, `expect(await stack.hooks.run(object, 'delete', { id }, opts)).toBe(true)`. If the test needs the deleted row, read it with `stack.rows(object, { id })` before the delete.

‎.claude/skills/pm-dispatch/references/landing-operations.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
- ready + 全绿 ≠ 已入队:队列从不主动拉 PR,入队是显式动作。
6565
- 零 `enqueued` 事件按序查三条:① `mergeable_state` 是否 `dirty`。
6666
- ② enable-auto-merge 调用根本没落地,重发与效果验证序列见 `platform-readings.md`。
67-
- ③ PR 碰 `.github/workflows/**` 而 token 缺 workflows 权限。
67+
- ③ PR 碰 `.github/workflows/**`:令牌常驻 `workflows: write`(裁决评论 6104822731),仍拒即授权漂移。
6868
- 确认 MERGED 同一动作里给 `Part of` 卡收口、`Fixes` 卡代关但标也须摘,⛔ 不拆到下轮巡检。
6969
- 卡以 completed 关闭且带 `Adopts:` 行 ⇒ 同一动作在所指 fork PR 贴致谢与落地链接并关闭它。
7070
- `Part of` 卡开着不摘 `pm:dispatched`,无在飞物的卡就永远算进 `label:pm:dispatched is:open`。

‎.github/workflows/fleet-write.yml‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@
2020
# alone, that transfer's one target too: `transferIssue` needs issues write on
2121
# both ends) and to the permissions the op table declares (`issues: write`,
2222
# `pull-requests: write`, the `contents: write` GitHub's auto-merge mutations
23-
# require, and the `metadata: read` the sender gate's permission read needs;
23+
# require, the `workflows: write` that arming auto-merge on a pull changing a
24+
# workflow file requires, and the `metadata: read` the sender gate's permission read needs;
2425
# plus, for a `workflow_dispatch` stroke ALONE, the `actions: write` the
2526
# dispatch endpoint needs — minted from the validator's per-stroke output, so
26-
# every other run's token is exactly the four grants above)
27+
# every other run's token is exactly the five grants above)
2728
# — and revoked by that action's post step. The workflow's own `GITHUB_TOKEN` gets nothing
2829
# (`permissions: {}` below); the job grants it `contents: read` for the
2930
# checkout and nothing else, so the run's ONLY write path is the App token,
@@ -123,13 +124,20 @@ jobs:
123124
# The op table in `scripts/pm/fleet-write/ops.mjs` has NO contents op — no file, ref
124125
# or branch write — so this grant is spent only by those two mutations.
125126
permission-contents: write
127+
# ONE consumer: `automerge_enable` on a pull that changes `.github/workflows/**` —
128+
# GitHub refuses to enqueue such a pull for an App token without `workflows` (measured:
129+
# "refusing to allow a GitHub App to create or update workflow … without `workflows`
130+
# permission"). Literal on every stroke by the maintainer's ruling (comment 6104822731).
131+
# It also reaches workflow-file writes through contents and git endpoints; the op table
132+
# has no row for either, and `validate.mjs --self-test` pins that none is reachable.
133+
permission-workflows: write
126134
permission-metadata: read
127135
# ONE consumer: the `workflow_dispatch` row — `POST .../actions/workflows/{file}/dispatches`
128136
# on an allowlisted file at `main`. Minted ONLY for a stroke carrying that op: the validator
129137
# writes `permission_actions=write` for such a stroke and an EMPTY value for every other,
130138
# and `actions/create-github-app-token` skips an empty `permission-*` input (its
131139
# `lib/get-permissions-from-inputs.js`: an empty value leaves the set unchanged, measured
132-
# at the v3 tag), so every other run keeps exactly the four grants above. `actions: write`
140+
# at the v3 tag), so every other run keeps exactly the five grants above. `actions: write`
133141
# also reaches cancel, re-run, delete, cache and artifact endpoints; the op table has no
134142
# row for any of them, and `validate.mjs --self-test` pins that none is reachable.
135143
permission-actions: ${{ steps.validate.outputs.permission_actions }}

‎content/docs/permissions/authorization.mdx‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,11 @@ package (metadata); subject bindings and env-specific values stay as config.**
164164
env-admin-only construct.
165165
- This is what makes package **uninstall** well-defined (drop the package's
166166
own sets) and the objectui Access matrix scopable to `{ packageId }`.
167-
- Declared positions and sharing rules seed the same way
168-
(`bootstrapDeclaredPositions`, ADR-0057 D6) — a declarable-but-never-seeded
169-
array is exactly the inert-metadata smell ADR-0078 prohibits.
167+
- Declared sharing rules seed the same way (ADR-0057 D6) — a
168+
declarable-but-never-seeded array is exactly the inert-metadata smell
169+
ADR-0078 prohibits. Declared positions need no row at all: the security
170+
catalog serves them from the registry, and no boot step writes a
171+
`sys_position` row (ADR-0131 D2).
170172

171173
### Package capability declaration (ADR-0066 D1)
172174

@@ -272,7 +274,7 @@ one of two doors, each writing only what it owns:
272274

273275
| Stage | What holds today | Owned by |
274276
|---|---|---|
275-
| 1 · Package development | Zod-validated authoring; positions / sharingRules / permissions seeded at boot with provenance | ADR-0057 D6, ADR-0086 D5, ADR-0049/0078 gates |
277+
| 1 · Package development | Zod-validated authoring; sharingRules / permissions seeded at boot with provenance; positions served from the registry, with no row (ADR-0131 D2) | ADR-0057 D6, ADR-0086 D5, ADR-0131 D2, ADR-0049/0078 gates |
276278
| 2 · Distribution / install / upgrade / uninstall | Install-consent scopes (ADR-0025 — consent ≠ RBAC grants); namespaced, collision-free composition; provenance axis makes uninstall well-defined | ADR-0025/0028/0048/0086 |
277279
| 3 · Environment composition / assignment | Platform-owned assignment records (`sys_user_position` etc.); anti-escalation; union semantics | ADR-0057 D4 |
278280
| 4 · Runtime enforcement | The six-gate chain above; each enforced primitive holds a row naming its enforcement site — the matrix file is the authority on the count and on which rows CI can check | ADR-0056 D10 matrix |

‎content/docs/permissions/permission-metadata.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ The built-in context variables available in RLS `using`/`check` clauses include
220220
(ADR-0068). `positions` is a **string array** — the canonical assignment field
221221
(D3 reserves the word "role"; there is no `current_user.roles`) — so test
222222
membership with CEL: `'org_admin' in current_user.positions` or
223-
`current_user.positions.exists(p, p == 'sales_manager')`. The framework-seeded
223+
`current_user.positions.exists(p, p == 'sales_manager')`. The framework-declared
224224
built-in position names are `platform_admin`, `org_owner`, `org_admin`, and
225225
`org_member`; `everyone`/`guest` are the implicit audience anchors (ADR-0090 D9).
226226
(`current_user.isPlatformAdmin` is the `PLATFORM_ADMIN` standing of

‎content/docs/permissions/positions.mdx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ including holders of plain CRUD on the table — is denied.
8080
## Built-in identity positions (ADR-0068)
8181

8282
`current_user.positions` is a string array — the only canonical field (there
83-
is no singular `current_user.position` at runtime). Framework-seeded names:
83+
is no singular `current_user.position` at runtime). The built-in names are
84+
declared by the platform — served by the security catalog, with no
85+
`sys_position` row (ADR-0131 D2):
8486

8587
| Position | Meaning |
8688
|:---|:---|

0 commit comments

Comments
 (0)