Skip to content

Commit 308a340

Browse files
committed
Merge origin/main into claude/issue-19082-summary-index-loud-skip
2 parents d413c5e + e6a03e6 commit 308a340

5 files changed

Lines changed: 353 additions & 20 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/plugin-hono-server': patch
3+
---
4+
5+
`/auth/me/permissions` now answers a wildcard-only `viewAllRecords` principal instead of staying silent about every object it can reach.
6+
7+
`seedSuperUserRestrictedObjects` was guarded to `modifyAllRecords` super-users alone. A principal that reaches an object only through a wildcard `viewAllRecords` grant therefore got **no entry at all**: the client fell back to its default-allow path and rendered write and Export affordances the server answers `403 EXPORT_NOT_PERMITTED`. Same silence, same consequence, different principal class from the one framework#18931 closed.
8+
9+
- **One predicate admits both classes.** The seed now asks the wildcard READ bypass — `viewAllRecords || modifyAllRecords` — which is the same question `foldWildcardSuperUser` already asks to decide whose `allowRead` it pulls true, and the same one `PermissionEvaluator` applies server-side. It is now a single module-local reading both call sites share, so the seed can never materialise an entry for a principal the fold leaves entirely false.
10+
- **A plain wildcard grant carrying neither bypass bit is still not seeded.** That is what makes the admission the read bypass rather than "any wildcard": the fold pulls nothing true for it, so a seeded entry would be an all-false claim with no server behaviour behind it.
11+
- **The seeded entry is the truth, not an overreach.** It starts `{allow*: false}`, the fold pulls `allowRead` true, and the write bits stay false. The seed only ever touches objects with **no explicit entry**, and on those a viewAll-only principal really can only read — so "explicit false" for edit is what is true about it, where the silence it replaces was not.
12+
- **`apiOperations` is attached through the predicate already shared with the modify-all class** — an unrestricted object whose export stays allowed is still skipped, because for it the client's default-allow path is already right.
13+
14+
⚠️ **This is a deliberate behaviour change on an existing published channel, ruled rather than inferred.** For a viewAll-only principal a client that reads "no entry" as default-allow now reads an explicit `allowEdit: false` instead. Two pins asserting the old silence (`toBeUndefined` for the viewAll-only principal, one of them added by the framework#18931 PR that pinned this boundary while saying the pin was not a ruling that the silence was correct) are inverted on purpose under that ruling. Payload growth is the same one-entry-per-object framework#18931 accepted, now also for viewAll principals.
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
name: Platform Checklist Status
2+
3+
# Publishes the platform's capability list AND its implementation status — the
4+
# maintainer's 「一个可以人工阅读确认的入口」 for 「平台真的功能清单,以及实现状态」.
5+
#
6+
# ## Why a schedule publishing to the WIKI, and not a page in the tree
7+
#
8+
# The entry point is GENERATED, never hand-written: the source is
9+
# `docs/qa/platform-checklist/areas/*.json`, and `pnpm gen:checklist-status` is
10+
# the only thing that ever states a count. Three shapes were ruled out on the
11+
# card before this one, and each rejection is load-bearing here:
12+
#
13+
# * a `STATUS.md` committed to this tree — a third artifact to keep fresh,
14+
# whose stale copy reads exactly as authoritative as a current one;
15+
# * a `check:checklist-status` gate pairing that page against the generator —
16+
# ⛔ explicitly ruled out: staleness here is TOLERATED, not gated, and an
17+
# unrelated PR is never blocked by checklist drift (the same standing
18+
# decision that keeps `check:platform-checklist` out of per-PR CI);
19+
# * a regen-on-edit rule on whoever touches an area JSON — a rule nobody can
20+
# enforce is a rule that silently stops being followed.
21+
#
22+
# What is left is a schedule. The wiki is one stable URL, outside branch
23+
# protection and the merge queue, so publishing there costs no review and
24+
# blocks no pull request. ⛔ This workflow therefore writes to the WIKI ONLY —
25+
# it never commits to this repository, never opens an issue, never labels
26+
# anything.
27+
#
28+
# ## The page set — one area JSON, one wiki page
29+
#
30+
# One INDEX page (`Platform-Checklist`): a row per area — which is a row per
31+
# definition item — carrying its active count, its planned count and a link.
32+
# Plus one `Checklist-<area>` page per area listing every item as
33+
# `id · title · priority · status · personas`, with the `planned` items in their
34+
# OWN section, FIRST. The count of pages is a property of the ledger and is
35+
# never asserted here: the generator writes what it reads.
36+
#
37+
# ## ⛔ Why this file lands by a human merge
38+
#
39+
# `.github/workflows/**` is not on the governed register, but the seat token
40+
# that opens this PR lacks the `workflow` scope, so the maintainer merges it by
41+
# hand. It is deliberately the ONLY thing in its pull request — the ledger
42+
# change it reports on lands separately, through the merge queue.
43+
#
44+
# ⚠️ ORDERING: `pnpm gen:checklist-status` is added by that sibling PR. Merging
45+
# this file first leaves a scheduled job that fails until the sibling lands —
46+
# loudly, which is the right direction, but it is avoidable by merging the
47+
# ledger PR first.
48+
49+
# This workflow is paths-filtered (the `pull_request:` trigger below), so
50+
# `scripts/pm/dispatch-gates.mjs` requires it to discover a `check:` family or
51+
# say why it has none. It has none, and that is the maintainer's ruling rather
52+
# than an omission: the whole point of publishing on a schedule is that the
53+
# paired gate was refused. The declaration is the one line under this
54+
# paragraph — it owns that line alone, so ⛔ do not wrap its reason or write a
55+
# comment directly beneath it; a continuation line is read as a CUT reason and
56+
# refused by name.
57+
# dispatch-gates: no-check-families -- the only step that runs anything is `pnpm gen:checklist-status`, a GENERATOR that renders the wiki pages; a paired `check:checklist-status` was explicitly ruled out (staleness of this reading entry is tolerated, not gated), so no named local check family exists for this workflow to discover
58+
on:
59+
schedule:
60+
# Weekly, as ruled. The ledger moves at the pace of platform capabilities,
61+
# not of pull requests, and this page is a reading entry rather than an
62+
# alarm — a day of staleness on it costs nothing a reader can trip over.
63+
#
64+
# The minute is offset off the top of the hour ON PURPOSE (scheduled
65+
# workflows queue behind everyone else's `:00` cron) and off the sibling
66+
# patrols' minutes, so two scheduled jobs never contend for one runner
67+
# minute.
68+
- cron: '37 3 * * 1'
69+
# The manual fire — and the smoke test this card is accepted on.
70+
workflow_dispatch: {}
71+
# ⛔ PATHS-FILTERED TO THIS ONE FILE, and that filter is the whole reason this
72+
# trigger may exist. It fires only on a pull request editing this workflow, so
73+
# it blocks no unrelated pull request at all — the posture every patrol in
74+
# this repo keeps. ⛔ Do NOT widen this list: a second path would put this job
75+
# on the critical path of pull requests that have nothing to do with it.
76+
#
77+
# ⛔ A pull_request run NEVER writes to the wiki. It proves the generator, the
78+
# invocation and the rendering on a real runner, and stops there — see the
79+
# `if:` on the publish step.
80+
pull_request:
81+
paths:
82+
- '.github/workflows/checklist-status.yml'
83+
# ⛔ NO `merge_group:` and ⛔ NO `pull_request_target:` here, ever. Neither is
84+
# made safe by a `paths:` filter, and this job holds a token that can write.
85+
86+
# Least privilege. `contents: write` is what pushing to the repository wiki
87+
# needs; this job writes nothing else — no issue, no label, no comment, and no
88+
# commit to the repository itself.
89+
permissions:
90+
contents: write
91+
92+
# One publisher at a time. A scheduled run overlapping a manual dispatch would
93+
# have two clones racing to push the same pages, and the loser would fail on a
94+
# non-fast-forward for no reason a reader could act on.
95+
concurrency:
96+
group: checklist-status-publish
97+
cancel-in-progress: false
98+
99+
env:
100+
# The index page's name, which `docs/qa/platform-checklist/README.md` links
101+
# and the generator writes. Restated here only so the publish step can report
102+
# the URL it wrote; the generator is the authority on the file names.
103+
INDEX_PAGE: Platform-Checklist
104+
105+
jobs:
106+
publish:
107+
# ⛔ This job is NOT a required context and must never become one: it runs on
108+
# no pull request except one editing this file, so a ruleset naming it would
109+
# block every other PR forever on a check that cannot report.
110+
name: Publish the checklist status pages
111+
runs-on: ubuntu-latest
112+
timeout-minutes: 15
113+
steps:
114+
- name: Checkout repository
115+
uses: actions/checkout@v7
116+
117+
# Kept as this job's own step rather than folded into the composite below:
118+
# `scripts/check-node-version.mjs` scans `.github/workflows/*.yml` only and
119+
# reports how many setup-node steps it audited, so a step moved out of
120+
# sight would silently shrink its census.
121+
- name: Setup Node.js
122+
uses: actions/setup-node@v7
123+
with:
124+
node-version: '22'
125+
126+
# `pnpm` itself is the requirement, NOT the workspace. The generator is
127+
# invoked through its PACKAGE SCRIPT (`pnpm gen:checklist-status`), which
128+
# runs its `--self-test` first: an inlined `node scripts/...` copy would
129+
# drop that leg, and the whole product of this job is a page full of
130+
# numbers that nothing downstream disagrees with.
131+
- name: Setup pnpm
132+
uses: ./.github/actions/setup-pnpm
133+
134+
# No `pnpm install`. The generator imports `node:` builtins and one
135+
# repo-local module — no npm dependency — so installing the workspace
136+
# would buy nothing and would give an unattended weekly run a lockfile it
137+
# could fail on.
138+
- name: Generate the status report and the wiki pages
139+
id: gen
140+
run: |
141+
set +e
142+
pnpm gen:checklist-status --out "$RUNNER_TEMP/wiki-pages" \
143+
> "$RUNNER_TEMP/status.out" 2> "$RUNNER_TEMP/status.err"
144+
code=$?
145+
set -e
146+
# Captured with NO pipe in between. `cmd | tail` reports the PIPE's
147+
# status -- `tail` essentially never fails -- so a red generator and a
148+
# green one would both read as 0.
149+
echo "exit_code=$code" >> "$GITHUB_OUTPUT"
150+
echo "pnpm gen:checklist-status exited $code"
151+
if [ "$code" -ne 0 ]; then
152+
cat "$RUNNER_TEMP/status.out" "$RUNNER_TEMP/status.err" 2>/dev/null || true
153+
exit "$code"
154+
fi
155+
pages=$(find "$RUNNER_TEMP/wiki-pages" -maxdepth 1 -name '*.md' | wc -l | tr -d ' ')
156+
echo "pages=$pages" >> "$GITHUB_OUTPUT"
157+
# A refusal, not a pass. "the ledger is empty" and "the generator
158+
# stopped reading it" produce the same zero, and the second one must
159+
# never be published as the platform's capability list.
160+
if [ "$pages" -lt 2 ]; then
161+
echo "gen:checklist-status produced $pages page(s): an index plus one page per area is at least 2."
162+
echo "This is a REFUSAL, not a pass -- publishing here would replace the wiki with a reading nobody took."
163+
exit 1
164+
fi
165+
166+
- name: Mirror the report into the job summary
167+
# Always: this makes the run self-contained for whoever opens it, and on
168+
# a pull_request run (which publishes nothing) it IS the whole product.
169+
if: always()
170+
run: |
171+
{
172+
echo "### Platform checklist status — generator exit ${{ steps.gen.outputs.exit_code }}"
173+
echo
174+
if [ "${{ github.event_name }}" = "pull_request" ]; then
175+
echo "_Wiki write skipped: a pull_request run proves the generator without publishing anything._"
176+
echo
177+
fi
178+
echo '```'
179+
cat "$RUNNER_TEMP/status.out" 2>/dev/null || echo '(no stdout captured)'
180+
cat "$RUNNER_TEMP/status.err" 2>/dev/null || true
181+
echo '```'
182+
} >> "$GITHUB_STEP_SUMMARY"
183+
184+
- name: Publish the pages to the repository wiki
185+
# ⛔ NEVER on a pull_request run, whatever the generator said.
186+
if: steps.gen.outputs.exit_code == '0' && github.event_name != 'pull_request'
187+
env:
188+
WIKI_URL: https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.wiki.git
189+
run: |
190+
set -euo pipefail
191+
# Absence must be LOUD (AGENTS.md "Route & surface ownership" §3): an
192+
# uninitialised wiki cannot be cloned, and a job that quietly skipped
193+
# the publish would report success forever while nothing was ever
194+
# published. Name the remedy instead.
195+
if ! git clone --depth 1 "$WIKI_URL" "$RUNNER_TEMP/wiki" 2> "$RUNNER_TEMP/clone.err"; then
196+
sed -E 's#x-access-token:[^@]*@#x-access-token:***@#g' "$RUNNER_TEMP/clone.err" || true
197+
echo "Could not clone the repository wiki."
198+
echo "REMEDY: the wiki must exist before it can be written to. Open the repository's Wiki tab and create the first page once (any content); every run after that overwrites the generated pages."
199+
exit 1
200+
fi
201+
cd "$RUNNER_TEMP/wiki"
202+
git config user.name 'github-actions[bot]'
203+
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
204+
205+
# Remove the pages this generator OWNS and no others: an area deleted
206+
# from the ledger must not leave its page standing, and a hand-written
207+
# wiki page must not be collateral. Ownership is the naming rule the
208+
# generator uses, and nothing else.
209+
find . -maxdepth 1 -type f \
210+
\( -name "${INDEX_PAGE}.md" -o -name 'Checklist-*.md' \) -delete
211+
cp "$RUNNER_TEMP"/wiki-pages/*.md .
212+
213+
if git diff --quiet && git diff --cached --quiet && [ -z "$(git status --porcelain)" ]; then
214+
echo "wiki already matches the ledger — nothing to publish."
215+
exit 0
216+
fi
217+
git add -A
218+
git commit \
219+
-m 'docs(wiki): regenerate the platform checklist status pages' \
220+
-m "Generated by .github/workflows/checklist-status.yml from docs/qa/platform-checklist/areas/*.json at ${GITHUB_SHA}. Do not edit these pages -- edits go to the area JSON."
221+
git push origin HEAD
222+
echo "published ${{ steps.gen.outputs.pages }} page(s) to ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/wiki/${INDEX_PAGE}"
223+
224+
- name: Fail the run if the generator left no reading
225+
# LAST, on purpose, and narrow: the one state that must never render as
226+
# a healthy run is a generate step that finished without recording an
227+
# exit code at all. Nothing downstream could tell that apart from
228+
# `exit_code=0`, and "the ledger was not read" must never publish as
229+
# "the platform has no capabilities".
230+
if: always() && steps.gen.outputs.exit_code == ''
231+
run: |
232+
echo "the generate step recorded no exit code, so this run is NOT a reading about the checklist. See this run's summary."
233+
exit 1

0 commit comments

Comments
 (0)