Skip to content

Commit cb86024

Browse files
committed
Merge origin/main into claude/issue-21458-approval-action-acted-as
Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
2 parents d576d5b + 49524f6 commit cb86024

60 files changed

Lines changed: 4819 additions & 345 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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/lint': patch
3+
---
4+
5+
Flow, hook, action, approval and expression rule findings no longer cite tracker numbers; each one states the decision behind it in words
6+
7+
Clause-②: no
8+
9+
Some findings these rules show to authors through `os validate`, `os lint` and `os build`, and the startup-registry findings a plugin author reads, pointed at an issue-tracker number for the reason behind them. The number goes; where the sentence did not already say what was decided, it now does.
10+
11+
- Flow patterns: the record-change date-equality hint and the date-equality filter hint name the declarative alternative, a `schedule` flow whose start node carries a `config.timeRelative` descriptor; the unscoped `runAs` hint says `runAs` is enforced, so a run with no trigger user has its data operations refused rather than run unscoped; the unbounded bulk-write hint says `multi: true` is how a flow declares bulk intent and that the engine admits a whole-object write declared that way; the revise-target hint says the run-resume route continues a pause on a service-owned node type only through the service that owns it; the two interpolation hints say a flow node value is a string template in which only single-brace tokens resolve.
12+
- Startup-registry findings: the open-vocabulary notes say the engine judges node types only once the vocabulary is sealed at `kernel:bootstrapped`; the prescription describes the lazy cache resolution and the ADR-0104 attestation by what each does; the assertive-wording finding describes its two incidents, and how each was fixed, in words.
13+
- Expression findings: the field-level `visibleWhen` consequence names the `current_user` binding ADR-0089 D1 gives every runtime record surface; the retired `script` keys finding says spec 17 made `script` a call to a registered function and nothing else.
14+
- Trigger readiness: the array `triggerType` hint says multi-event arrays are deferred until two independent projects need a combination other than created-or-updated.
15+
- Body writes, readonly writes and approvals: the discarded `ctx.record` write says the snapshot stays read-only by design and an action writes through `ctx.api`; the `readonlyWhen` write finding says a bulk update strips the field from every matched row once any one of them is locked; the `queue` approver finding says the type was deprecated rather than built; the empty-slate hint says the admin override may act on any pending request, so that one nobody in its slate can decide never stays stuck.
16+
- The other findings drop a citation the sentence already explained.
17+
18+
Text only: no rule id, severity, condition or finding moves. A tool or test that matches the old text (for example a tracker-number suffix) needs the new spelling.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'@objectstack/metadata-protocol': minor
3+
'@objectstack/objectql': minor
4+
'@objectstack/mcp': minor
5+
'@objectstack/plugin-audit': minor
6+
'@objectstack/service-analytics': minor
7+
'@objectstack/cli': minor
8+
---
9+
10+
fix(metadata-protocol)!: a metadata body's stored content hash is served and compared only in keyed form, never copied, and never evaluated (#21207)
11+
12+
Clause-②: yes (narrowing)
13+
14+
<!-- adr-0087: not-required (no-migration-prescription) the stored content hash of a metadata body stays the canonical hash at rest and no metadata body, authorable key, spelling or export moves; what changes is the form a door serves the hash in (a keyed digest: the crypto provider's, or a process-scoped ephemeral key's when none is registered), the form an inbound version token is compared in, and which query shapes the doors accept over the two hash columns, so `objectstack migrate meta` has nothing to rewrite. The operator-run rewrite this release asks for is of audit, activity and decision-audit copies, not of metadata. The other categories are closed on facts: every package here publishes (not `unpublished`); no ADR-0087 id covers a served version token or a refused query shape (not `registered` / `already-registered`); and the change is runtime behaviour, not a declaration (not `runtime-interface-only` / `type-surface-only`). -->
15+
16+
**BREAKING**: this narrows what the metadata doors serve and accept for the stored content hash of a metadata body — a hash over the whole stored body, withheld credential material included. Served beside the projected body it let a reader confirm a guess at that material offline; filtered on, it confirmed one online. It ships as `minor` under the launch-window convention for accept-set narrowings.
17+
18+
**Three things change for callers and operators.**
19+
20+
1. **A held version token gets one `409 METADATA_CONFLICT`.** Every door that hands out a metadata version token — the save, publish, package-publish and rollback receipts and the history read — now hands out a keyed digest of the stored hash instead of the hash itself, and the save and reset doors compare a token they are sent in that same form. The key is the crypto provider's; a host that registers none keys under a process-scoped ephemeral key instead, so a token is always issued and never empty. A token a client held from before the upgrade is refused once; take the token from the next read or receipt and retry. On a host with no provider the same happens after a restart, and on any host when a provider is first registered. An empty, withheld, raw or stale token is refused with the same `409`; it is never read as "no pin".
21+
2. **Filter, sort and group on the two stored content-hash columns, and on the version history's change note, now answer `400 INVALID_FIELD`** — on the generic data door, the MCP stdio reader and the analytics door, before the engine runs. The change note is included because a draft promotion that stated no message of its own recorded the draft's stored hash in it; the publish door now always states a hash-free message, and a note written before this release is served with the quoted hash in keyed form. A data-door search over the two stored-metadata tables no longer scans those columns or the stored body column, and an explicit search-field list naming one answers the same `400`. Every other column of the two tables is served, filtered, sorted and grouped as before, and every other object is unchanged.
22+
3. **Operators run `os migrate audit-metadata-bodies` once after upgrading, dry run first.** The audit ledger, the activity feed and the metadata decision-audit trail no longer copy the stored hash. The extended command drops it from the copies already written and withholds it in the decision-audit notes and their copies: a dry run by default, `--apply` to rewrite, idempotent. The version history stays the lineage.
23+
24+
**What else changes.** The data door serves the two hash columns of the stored-metadata tables in keyed form, under the same key as the version tokens. The MCP stdio reader serves them keyed under the crypto provider's key, and omits them on a host with no provider. A `409` conflict refusal carries keyed values or none. The ObjectQL engine gains a read accessor for the registered provider's keyed digest; it is additive. A member's read of these tables is refused as before.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/plugin-sharing': minor
3+
'@objectstack/spec': patch
4+
---
5+
6+
feat(plugin-sharing): the record owner and an explicit Modify-All holder may mint a share link on a record the data door refuses them (ADR-0111 D8 rule 1, ruling A′) (#21329)
7+
8+
Clause-②: yes (widening)
9+
10+
- **Who may mint.** `ShareLinkService.createLink` admits the caller when they can see the record, **or** own it, **or** hold `modifyAllRecords` on the object. The object's `publicSharing` opt-in is still checked first, and `publicSharing.eligibility` still last. On an object declared `access: { default: 'private' }` no wildcard grant covers the record, so its owner's own read is refused; the owner can now share it anyway. A member who neither sees nor owns the record is refused exactly as before, with the same envelope.
11+
- **Who still needs visibility.** A hierarchy manager whose write depth covers the record's owner manages the record's shares (revoke, grant, list), but is not admitted to mint without seeing the record: a link creates access.
12+
- **The organization wall.** Under the `group` and `isolated` tenancy postures the owner and Modify-All alternatives are withheld and visibility alone admits, as before this release. A member who left an organization still owns the records they created there, and must not be able to publish them by link.
13+
- **A required capability.** Neither alternative applies past a capability the object requires (`requiredPermissions`). An owner or Modify-All holder who lacks it is refused with the capability gate's own refusal, as before this release; an owner who holds it, refused only because no permission set grants the object, mints. The verdict is read from the `required_permissions` layer of `ISecurityService.explain`, so a security service the sharing service reaches must implement `explain`. If it does not, the two alternatives are withheld.
14+
- **API.** `SharingService.canMintWithoutVisibility(object, recordId, context)` answers the two alternatives with the owner and Modify-All branches `canManageShares` reads. `ShareLinkServiceOptions.canMintWithoutVisibility` is the late-bound probe `createLink` asks once the visibility read refuses, and `SharingServicePlugin` wires it. A host that constructs `ShareLinkService` itself without it keeps the visibility rule alone. The probe slice `SharingServiceOptions.securityService` returns gains an optional `explain`, the part of `ISecurityService.explain` the capability verdict reads.
15+
- **`@objectstack/spec` (documentation only).** The `IShareLinkService.createLink` TSDoc states who may mint, replacing "you may only link-share a record you can yourself see". The `ISharingService.canManageShares` TSDoc describes the hierarchy-manager branch, which is implemented, and says it is not mint authority. No schema, key, type or export changes.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@objectstack/rest': patch
3+
---
4+
5+
Withdrawing a public form from anonymous intake now takes effect on every intake door
6+
7+
Clause-②: no
8+
9+
When an administrator withdraws a public form, both anonymous form routes (`GET /forms/:slug` and `POST /forms/:slug/submit`) now answer `404 FORM_NOT_FOUND` and no record is created. Republishing the form restores both routes. If a service the routes need to resolve the form is registered but cannot be reached, both routes refuse the request instead of serving the form.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/lint': patch
3+
---
4+
5+
`field-no-consumers` no longer calls a field "inert" when a dataset or cube member reads it through a relationship path (#21439).
6+
7+
Clause-②: no
8+
9+
`os validate`, `os build` and `os lint` warned "Verdict: inert — no site of any kind names it" for every field an analytics member reached through a path such as `account.revenue`, so an author following the warning would delete a column a measure reads. The four slots that name a column are a dataset dimension's and measure's `field` and a cube dimension's and measure's `sql`. Each one now credits every field its path reads: the lookup on the base object, each intermediate lookup, and the column on the object the last hop reaches.
10+
11+
- **Hops resolve the way the analytics door resolves them.** A cube hop goes through the join the cube declares for it, else the lookup's `reference`. A dataset hop goes through the `reference` its compiler joins through, and only where the dataset's `include` declares the join.
12+
- **A bare cube column is credited too.** Before, a cube member's `sql: 'amount'` credited nothing, because a cube names its object in its own `sql`.
13+
- **A path the door refuses reads nothing.** Examples: a join the dataset's `include` does not declare, a hop that names no relationship, a column the last object does not have. Each field such a path names is now listed as a carrier site that a removal must clean (`carrier-only`), not as a reader.
14+
- **A path the object graph cannot judge** credits the fields it does resolve. An example is a lookup to an object this stack does not define.
15+
16+
Nothing new is refused, and the rule stays a warning. One warning can appear where there was none: a path the door refuses through a lookup named after its target object (`account.revenue`, with `account` a lookup to the object `account`). The old text scan credited its column as read. It is now reported `carrier-only`, beside the error the refused path already carries.

‎content/docs/protocol/objectql/security.mdx‎

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -477,12 +477,24 @@ publicSharing:
477477
```
478478

479479
**Who may mint and revoke** (ADR-0111 D8). Minting a link requires the object's
480-
`publicSharing` opt-in **and** that the caller can see the record — an opted-in
481-
object deliberately delegates re-share power to anyone who can view the row.
480+
`publicSharing` opt-in **and** authority over the record: the caller can see it,
481+
**or** owns it, **or** holds `modifyAllRecords` on the object. An opted-in object
482+
deliberately delegates re-share power to anyone who can view the row; the owner
483+
and the Modify-All holder may mint even where the data door refuses them, as on
484+
an object declared `access: { default: 'private' }`, which no wildcard grant
485+
covers — so a member can share a record of their own there. A hierarchy manager
486+
whose write depth covers the owner is **not** admitted without visibility: a
487+
link creates access, so they need to see the record to mint one. Under the
488+
`group` and `isolated` tenancy postures the owner and Modify-All alternatives
489+
are withheld and visibility alone admits, so no mint crosses the organization
490+
wall. Nor do they apply past a capability the object requires
491+
(`requiredPermissions`): a caller who lacks it is refused even on a record they
492+
own. The opt-in is checked first and `eligibility` (below) last.
482493
Revoking a link is allowed for the link's **creator**, a **record share-manager**
483-
(the record's owner or a `modifyAllRecords` admin — the same authority as
484-
`canManageShares`), or system context: a link someone else minted on your record
485-
is your record's exposure to kill, not only its creator's.
494+
(the record's owner, a `modifyAllRecords` admin, or a hierarchy manager whose
495+
write depth covers the owner — the same authority as `canManageShares`), or
496+
system context: a link someone else minted on your record is your record's
497+
exposure to kill, not only its creator's.
486498

487499
**When `eligibility` is enforced** (#13608). The optional `eligibility` CEL
488500
predicate is a **standing policy about which records may be reached

0 commit comments

Comments
 (0)