Skip to content

Add the RealmPolicy, PolicyRule and OperationGrant definitions and PolicyPredicateField - #751

Merged
habdelra merged 6 commits into
mainfrom
cs-13167-plan-let-boxel-repo-tests-use-select-card-definitions-that
Sep 24, 2026
Merged

habdelra merged 6 commits into
mainfrom
cs-13167-plan-let-boxel-repo-tests-use-select-card-definitions-that

Conversation

@habdelra

@habdelra habdelra commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Background and Goal

This adds the system-level policy definitions to the catalog realm. realm-policy/realm-policy.gts holds RealmPolicy, PolicyRule and OperationGrant, the shapes that the realm's operation-permission checks read. fields/policy-predicate/policy-predicate.gts holds PolicyPredicateField, the BXL where condition on a grant. Nothing reads a policy yet, so no behaviour changes. The field's policy-predicate serializer is platform code in boxel's runtime-common, and the field refers to it by key.

These are the first entries in boxel's catalog test subset (packages/catalog/test-subset.json in cardstack/boxel). That subset is a pinned selection of the catalog's system-level definitions, which boxel's test stacks serve as the catalog realm so the platform can be tested against the catalog's one copy.

Merge order

  1. Add the RealmPolicy, PolicyRule and OperationGrant definitions and PolicyPredicateField boxel#6276 adds the policy-predicate serializer and PolicyPredicate type these definitions use. This PR's Lint is red until it is on boxel main.
  2. This PR.
  3. Serve a pinned subset of system catalog definitions to boxel's test suites boxel#6277, re-pinned to this PR's merge commit.

Key decisions

  • .github/workflows/boxel-test-subset.yaml runs the boxel tests the subset manifest names whenever a PR here touches a subset file, using this checkout as the subset source. The deployed catalog serves this repo's main, so a subset change has to keep boxel's tests green before it merges.
    • A PR is tested against the boxel branch with the same name when one exists (here cs-13167-plan-let-boxel-repo-tests-use-select-card-definitions-that), and otherwise against boxel main. That lets a catalog change and the boxel change depending on it be validated together.
    • It skips when the boxel checkout has no manifest, and for PRs that touch no subset file.
  • Merge order: cardstack/boxel's branch cs-13077-policy-realmpolicy-policyrule-operationgrant-definitions-and merges first, because it adds the policy-predicate serializer and the PolicyPredicate type these definitions use. Until then, this PR's Lint fails on the missing export. This PR merges next. The boxel subset branch then re-pins to the merge commit, and its CI enforces that the pin is on this repo's main.

🤖 Generated with Claude Code

habdelra and others added 2 commits September 23, 2026 14:57
…licyPredicateField

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 23, 2026 19:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Staging Submissions Preview

This PR's content is pushed to the staging submissions realm: https://realms-staging.stack.cards/submissions/

Changed folders:

  • .github/
  • fields/
  • realm-policy/

Updated at 2026-09-24 14:01:27 UTC for commit d805670. Shared realm: only this PR's changed files are pushed; files touched by multiple PRs reflect whichever pushed last, and deleted files are not removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@backspace backspace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] This review checked two things: whether the new workflow's gate catches the catalog changes it exists to catch, and whether the two definitions match the policy-predicate serializer and the host tests on the boxel side. I did not review the boxel-side PRs themselves.

No blocking issues. There are two non-blocking fixes. Each is one line:

  1. Include previous_filename in the changed-file list, so that a renamed subset file runs the tests. See the thread on the scope step in boxel-test-subset.yaml.
  2. Make the predicate editor's placeholder match the empty-condition state. See the thread on @placeholder in policy-predicate.gts.

Lint is red only because the PolicyPredicate export is missing. That is expected until the boxel serializer change is on boxel main.

Comment on lines +85 to +89
gh api "repos/$GITHUB_REPOSITORY/pulls/${{ github.event.pull_request.number }}/files" \
--paginate --jq '.[].filename' > changed.txt
else
gh api "repos/$GITHUB_REPOSITORY/compare/${{ github.event.before }}...${{ github.sha }}" \
--jq '.files[].filename' > changed.txt

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] A PR that renames a subset file skips all subset tests. For a rename, .filename is the new path, and the old path is only in previous_filename. Nothing imports realm-policy/realm-policy.gts, so a rename of that file changes no other subset file. The new path matches nothing in subset.txt, and the gate reports "No subset file touched". The pin check on the boxel side also passes, because the pinned revision is still an ancestor of main. Thus the deployed catalog loses the definition and neither repo shows a red check. The compare API that the push path uses has the same field.

Suggested change
gh api "repos/$GITHUB_REPOSITORY/pulls/${{ github.event.pull_request.number }}/files" \
--paginate --jq '.[].filename' > changed.txt
else
gh api "repos/$GITHUB_REPOSITORY/compare/${{ github.event.before }}...${{ github.sha }}" \
--jq '.files[].filename' > changed.txt
gh api "repos/$GITHUB_REPOSITORY/pulls/${{ github.event.pull_request.number }}/files" \
--paginate --jq '.[] | .filename, (.previous_filename // empty)' > changed.txt
else
gh api "repos/$GITHUB_REPOSITORY/compare/${{ github.event.before }}...${{ github.sha }}" \
--jq '.files[] | .filename, (.previous_filename // empty)' > changed.txt

When the old path is in changed.txt, the jobs run, and the subset sync fails because the file is missing from this checkout. That is the red check that this case needs. Regression in the new workflow. Non-blocking, but this is the case that the workflow exists to catch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] Fixed in c88ab58. Both the PR-files path and the push compare path now emit previous_filename alongside filename. A rename or move of a subset file therefore counts as touching it, the jobs run, and the subset sync fails on the missing file in this checkout.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] Verified. This resolves it. Both paths now emit previous_filename for a rename, and a local jq test on a renamed entry gives the old path. The subset jobs ran and passed on c88ab58.

@value={{@model.source}}
@onInput={{this.setSource}}
@readonly={{not @canEdit}}
@placeholder='Always allowed'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] The editor shows "Always allowed" for an emptied condition. But this field keeps that state separate from always-allowed. When the author clears the text, the value is { source: '', snapshot }. That is a present predicate: View shows it as "empty condition", and the serializer keeps it as '', not null. The placeholder does not look at which of the two states it is in.

Suggested change
@placeholder='Always allowed'
@placeholder={{if @model 'Empty condition' 'Always allowed'}}

Regression. Non-blocking.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] Agreed. This file mirrors the boxel-side definition byte-for-byte except for its import specifiers, so the change goes into the boxel source first and is then ported here, rather than letting the two copies differ. I've passed the suggestion (@placeholder={{if @model 'Empty condition' 'Always allowed'}}) to that change. I'll reply here with the commit once it's ported.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] That order is correct. Keep the two copies the same. This item stays open until the port lands here. It does not block this change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] Ported in 0817dc0. @placeholder={{if @model 'Empty condition' 'Always allowed'}} now matches the boxel source, which gained the same change together with edit-test assertions for both placeholders. Apart from import specifiers and formatting, the two copies are again identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@backspace backspace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] This re-review checks the new commit against the two open threads from the last review.

No blocking issues. Approved.

  • Renamed subset file: resolved. Both the PR path and the push path now count the old path. The subset jobs passed on this commit.
  • Empty-condition placeholder: still open, not blocking. The fix goes into the boxel source first and then comes here.
  • Lint fails only on the missing PolicyPredicate export from @cardstack/runtime-common. That is expected until the boxel side ships.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@habdelra
habdelra requested a review from a team September 24, 2026 13:30

@richardhjtan richardhjtan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] This review covered the scope gate and the job setup in boxel-test-subset.yaml, and the two definitions against the boxel-side serializer and the Switch/BoxelInput APIs. It did not cover the boxel-side PRs.

No blocking issues. There is one non-blocking fix and one follow-up.

  1. Treat a push whose compare lists 300 files as touching the subset. See the thread on the push path of the scope step.
  2. Follow-up, not this PR: the host and realm-server jobs copy boxel's CI stack setup, and that copy has already drifted from boxel's own ci-host.yaml, which no longer installs dbus-x11/upower through apt. If boxel exposes this as a reusable workflow_call workflow that takes the catalog ref, this file shrinks to the scope job plus one call. Setup then has a single home, and a boxel infra change can no longer turn catalog PRs red where catalog authors can't fix it.

Lint is red only on the missing PolicyPredicate export from @cardstack/runtime-common. That clears once the boxel serializer change is on boxel main.

Comment on lines +88 to +89
gh api "repos/$GITHUB_REPOSITORY/compare/${{ github.event.before }}...${{ github.sha }}" \
--jq '.files[] | .filename, (.previous_filename // empty)' > changed.txt

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] On a push, the compare API lists at most 300 files, and --paginate doesn't return more (only the first page carries files). I checked this against this repo: a compare spanning 1,040 changed files returned exactly 300. A merge that touches a subset file among more than 300 changed files therefore reports "No subset file touched". Three merges on main have been that large. The simplest guard is to run whenever the list is full:

gh api "repos/$GITHUB_REPOSITORY/compare/${{ github.event.before }}...${{ github.sha }}" > compare.json
jq -r '.files[] | .filename, (.previous_filename // empty)' compare.json > changed.txt
# The compare API lists at most 300 files, so a full list may hide a subset file.
[ "$(jq '.files | length' compare.json)" -ge 300 ] && truncated=true

Then skip the grep gate when truncated is set. Regression in the new workflow. Non-blocking: the PR path uses the paginated files API (up to 3,000 files) and gates first, so this only weakens the post-merge backstop.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] Fixed in d805670. The scope step now treats a listing at the API's cap as touching the subset, and the same guard covers the PR path's 3,000-file cap:

  • push: .files | length ≥ 300;
  • PR: the PR's changed_files ≥ 3000.

A full list skips the grep gate, so the jobs run. The rename handling is unchanged.

The reusable-workflow follow-up (a single home for the stack setup the host/realm-server jobs copy from boxel) is tracked separately.

@richardhjtan richardhjtan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] Approving. The notes in my earlier review are non-blocking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@habdelra
habdelra merged commit 765b76c into main Sep 24, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants