Skip to content

Commit 7f4fd6c

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-21585-handler-hook-refusal
2 parents 53c5916 + 83b3d32 commit 7f4fd6c

26 files changed

Lines changed: 1798 additions & 173 deletions
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
Liveness ledger: a permission set's row-level security policy `label` and `description` (`rowLevelSecurity[].label` / `.description`) are now `live`, not `dead`. Studio's permission editor shows both on every policy. Ledger data and its generated count shard only.
6+
7+
Clause-②: no
8+
9+
- **What shows them.** These are display keys, so under the ledger's "Designer previews count as consumers" ruling, being shown to a human is the whole of their claimed effect. The Row-Level Security section of the permission editor (`PermissionAdvancedFacets` in objectui) now heads each policy card with the policy's `label` and, beneath it, its `description`, exactly as written. Both rows cite that reader at the `.objectui-sha` pin `89cad75d557`. The registered permission preview also draws both, but no route mounts it for `permission`, so it is not cited.
10+
- **Where the values come from.** Each row names its producer: the `permission` edit page registration and the Studio edit route that mounts it, the editor's `GET /api/v1/meta/permission/:name/layers` read, and this repo's shared layered answer (`createMetaLayeredAnswer`), which serves a permission set whole. The showcase's contributor permission set authors both keys on all three of its policies.
11+
- **Author-facing effect.** `os lint` / `os validate` no longer warn `liveness-dead-property` on a policy that sets `label` or `description`. A warning is not a refusal, so the accept set is unchanged.
12+
- **Still kept.** The re-grade reverses no ADR-0033 decision. Both rows stay docs-shaped annotation, deliberately kept and not `authorWarn`'d.
13+
- The regenerated liveness count is the `liveness/state-counts/permission.md` shard: `permission` has 38 live and 4 dead (was 36 and 6). The `view` container's own `label` stays `dead`.
14+
- ⛔ No schema, parse, `.describe()`, export or accept-set change.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/metadata-core': minor
3+
'@objectstack/metadata-protocol': patch
4+
'@objectstack/rest': patch
5+
---
6+
7+
Public forms on a walled tenancy posture: saving or publishing a view whose public form cannot take anonymous intake now tells the author why, on the response.
8+
9+
Clause-②: yes (widening)
10+
11+
On a walled posture (`group` or `isolated` in force), an open public form whose object is walled by an organization column cannot take an anonymous submission: the submission carries no organization, and an insert without one into a walled object is refused. The two anonymous form endpoints already answer such a form as a withdrawn one (`404 FORM_NOT_FOUND`), and the administrator's read of the view (`GET /meta/view/:name`) already states why in `_diagnostics.warnings`.
12+
13+
- **`@objectstack/metadata-protocol`**: saving the view (`PUT /meta/view/:name`) or publishing its draft (`POST /meta/view/:name/publish`, and a package's batch publish) now answers success with one `warning` advisory per such form, under `advisories`, with rule `public-form-intake-unavailable`. It is located at the form's `sharing` (for example `views[0].formViews.contact.sharing`), its `message` is the same text the administrator's read states, and its `hint` is the remedy: if the object's rows belong to no organization, declare `tenancy: { enabled: false }` on it. The write is never refused. The advisory reads the posture in force from the `tenancy` service, which is what the anonymous endpoints read: a single-posture deployment, a deployment whose walled posture is degraded to `single`, a deployment with no tenancy service, and a form bound to a tenancy-disabled object get no advisory, and a draft save is not judged. The publish refusal for an unstamped platform schedule flow still reads the requested posture, as before.
14+
- **`@objectstack/metadata-core`**: the intake-availability rule moved here from `@objectstack/rest` and is exported, so the anonymous endpoints, the administrator's read and the publish advisory read one answer: `anonymousFormIntakeUnavailability(object, posture, readObjectSchema)` (`null` when the form can take intake, otherwise the object, the posture and the wall column; it judges the object's effective schema, with the injected `organization_id`), `anonymousFormIntakePosture(tenancy)` (the posture in force, as a tenancy service reports it), `anonymousFormIntakeUnavailableMessage` and `anonymousFormIntakeUnavailableRemedy` (the reason and its remedy), `anonymousFormSharingPath` and `anonymousFormObjectName`, and the type `AnonymousFormIntakeUnavailable`.
15+
- **`@objectstack/rest`**: the anonymous form endpoints and the administrator's read import that rule instead of holding their own copy. Their answers are unchanged.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/metadata-protocol": patch
3+
---
4+
5+
An expanded view of a stored view container is reported as tenant-authored on an unscoped kernel, as it already was on an environment-scoped one: not resettable, and with no `code` layer
6+
7+
Clause-②: no
8+
9+
On an unscoped (control-plane) kernel, registry hydration registers each view a stored environment-wide container expands, under that view's own name. The container was registered with the tenant-authorship marker (`_provenance: 'org'`), and its expansions were not. An expansion of a container bound to a package therefore carried that package's id and no marker, and the registry's artifact lookup took it for a view the package ships. For such a name `getMetaItem` (`GET /api/v1/meta/view/NAME`) answered `resettable: true`, and `getMetaItemLayered` (`/layers`) answered the stored container's expansion as the `code` layer. The `code` layer was also wrong for an expansion of a package-less container. An environment-scoped kernel registers nothing, and answered `resettable: false` and `code: null`.
10+
11+
Each registered expansion now carries its container's marker, applied before the expansion's own artifact envelope, in the same order the container gets it. Where the container's own package ships a view of that name, that artifact's envelope still wins (ADR-0010 §3.3). Both kernels now give the same answer for every expanded name. Studio's reset affordance and its code-versus-overlay diff are drawn from these two values.
12+
13+
The save door is unchanged: it accepts a write by an expanded name on both kernels, as before, and the stored row then answers that name.

‎.github/workflows/release.yml‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,10 @@ jobs:
790790
# `contents: write` is for GitHub Releases, never for refs: this job runs
791791
# no `git push` of any kind.
792792
contents: write
793+
# `actions: read` is the Releases backfill's guard: the audit reads this
794+
# workflow's runs and their jobs to ask whether the same version's
795+
# publish is in flight in another run. Reads only, never a cancel.
796+
actions: read
793797
outputs:
794798
# "the docker job must build" — set only when npm ALREADY has this
795799
# version's WHOLE fixed group and its runtime image is missing.
@@ -1066,9 +1070,33 @@ jobs:
10661070
;;
10671071
esac
10681072
1073+
# ── the publish in flight, before any Releases backfill ───────────
1074+
# npm settles BEFORE the publish job reaches its own "Create GitHub
1075+
# Releases" step, so a landing audited in that window found the group
1076+
# complete and wrote the same Releases beside it: on 17.6.0 the
1077+
# publish of run 36955885276 wrote them 03:03:47Z -> 03:05:42Z, this
1078+
# backfill in run 36958423332 started at 03:04:37Z, and five tags got
1079+
# two Release objects each. The two writers are in different runs,
1080+
# so no `needs:` can order them; the publish's own run creates its
1081+
# Releases and D4 asset, and a landing after it finishes backfills
1082+
# whatever it did not. scripts/release-pending-publish.mjs `in-flight`
1083+
# (its --self-test, run by lint.yml, holds the rule) answers
1084+
# `in-flight` on anything it cannot read, so nothing is backfilled off
1085+
# a guess. It only ever leaves `releases-missing` unset: this step
1086+
# stays green and the image request below is not its business.
10691087
if [ "$releases_ok" = true ]; then
10701088
echo "GitHub Releases + ADR-0087 D4 asset are present for ${version}."
1089+
elif ! flight=$(GITHUB_TOKEN="$GH_TOKEN" node scripts/release-pending-publish.mjs in-flight --version "$version" --version-commit "$version_commit" --head "$SHA" --workflow release.yml); then
1090+
echo "::warning::${version}'s GitHub Releases or ADR-0087 D4 asset are incomplete, and whether its publish is still in flight could not be asked (reason above). Nothing is backfilled off a guess; the next landing reads again."
1091+
elif [ "$(jq -r '.state' <<<"$flight")" != 'clear' ]; then
1092+
jq -c . <<<"$flight"
1093+
if [ "$(jq -r '.reason' <<<"$flight")" = 'publish-in-flight' ]; then
1094+
echo "::notice::${version}'s GitHub Releases or ADR-0087 D4 asset are incomplete, but its publish is still in flight ($(jq -r '.detail' <<<"$flight")). That run creates them; nothing is backfilled beside it. A landing after it finishes backfills whatever it did not."
1095+
else
1096+
echo "::warning::${version}'s GitHub Releases or ADR-0087 D4 asset are incomplete, and whether its publish is still in flight could not be read ($(jq -r '.detail' <<<"$flight")). Nothing is backfilled off a guess; the next landing reads again."
1097+
fi
10711098
else
1099+
jq -c . <<<"$flight"
10721100
echo "::warning::${version} is on npm but its GitHub Releases or the ADR-0087 D4 asset are incomplete (#4900) — backfilling."
10731101
echo "releases-missing=true" >> "$GITHUB_OUTPUT"
10741102
fi

‎content/docs/deployment/validating-metadata.mdx‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ orthogonal to both, and no cell here can carry it; it is written out in
478478
| Declared enforcement that cannot run, **declared on the object being written** — a validation rule's regex / JSON Schema (#4762) and its `format` names (#5178) | ✓ | ✓ | ✓ | ✓ᵒ |
479479
| Declared enforcement that cannot run, **declared on another collection** — sharing-rule conditions (#4698), row-level-security predicates (#4983) | ✓ | ✓ | ✓ | — |
480480
| Platform-schedule `create_record` organization (#6285) | — | — | — | ✓ᶠ |
481+
| Public-form anonymous intake on this deployment's tenancy posture — advisory only (#21476) | — | — | — | ✓ᵛ |
481482
| Autonumber `{field}` interpolation | ✓ | ✓ | ✓ | ✓ᵒ |
482483
| View references — form targets, view-key collisions (#2554) | ✓ | ✓ | ✓ | — |
483484
| Flow authoring anti-patterns (#1874) | ✓ | ✓ | ✓ | ✓ᶠ |
@@ -594,11 +595,17 @@ The fourth door does not weaken that, because it is held to the CLI's verdicts
594595
rather than to its own: a test fails if a rule runs at the runtime publish gate
595596
but not on `os build` — the two publish verbs must not disagree. What that
596597
column narrows is which *types* it judges, never which *verdict* it reaches. The
597-
one deliberate exception is the platform-schedule row (#6285), runtime-only by ruling:
598-
both of its inputs are facts about the **deployment** (the organization this
599-
write lands in, and whether this deployment walls organizations), and a build
600-
machine's environment is a false signal for them — so `os build` must not judge
601-
it at all.
598+
deliberate exceptions are the two rows whose inputs are facts about the
599+
**deployment**, and a build machine's environment is a false signal for those —
600+
so `os build` must not judge them at all. The platform-schedule row (#6285) is
601+
runtime-only by ruling: its inputs are the organization this write lands in and
602+
whether this deployment walls organizations. The public-form intake row (#21476)
603+
reads the tenancy posture **in force**: on a walled posture, an open public form
604+
whose object is walled by an organization column cannot take an anonymous
605+
submission, so the anonymous form endpoints do not offer it, and a save or
606+
publish of the view answers success with a `public-form-intake-unavailable`
607+
warning in `advisories`, located at the form's `sharing`. It never refuses the
608+
write.
602609

603610
Some rows are deliberately not universal across the three commands, and each is
604611
one-directional (none lets a stack through a gate another command enforces):

‎content/docs/ui/forms.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export default defineView({
100100
> - Anything not in the `sections[].fields[]` whitelist is silently stripped at submit time. Treat the whitelist as the form's authoritative "what the public is allowed to set" list.
101101
> - A form whose sections declare **no** fields collects nothing, so the submit is **refused** (`400 VALIDATION_ERROR`) rather than accepting whatever the caller sent (#6920). Its `GET /forms/:slug` publishes no schema either (#6601) — declare the fields and both planes come alive together.
102102
> - Multiple form views per object are fine — only the one(s) with `sharing.enabled === true` and `sharing.allowAnonymous === true` are exposed.
103+
> - On a **walled** tenancy posture (`group` or `isolated` in force), a form whose object is walled by an organization column is **not offered**. An anonymous submission carries no organization, and an insert without one into a walled object is refused, so both anonymous endpoints answer the form exactly as they answer a withdrawn one (`404 FORM_NOT_FOUND`). The administrator is told why, at the form's `sharing`: the view's read (`GET /api/v1/meta/view/:name`) carries it in `_diagnostics.warnings`, and a save or publish of the view answers success with a `public-form-intake-unavailable` warning in `advisories`. If the object's rows belong to no organization, declare `tenancy: { enabled: false }` on it and the form is offered again.
103104
104105
## 2. (Optional) Create the `guest_portal` permission set
105106

0 commit comments

Comments
 (0)