Repository navigation
Features batch 3: bulk-write audit, worker images, server head, SPA mounts, resource access (#557 #540 #535 #541 #526) - #574
Merged
Conversation
Head DTO + inertia.render(head=), escaped tags with data-inertia keys, _head prop re-emitted via Inertia serverHead, public_base_url host setting (never the request Host), both index.html templates, docs, tests. Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
# Conflicts: # CHANGELOG.md
…ResourcePolicy, RequiresResource (#526) ResourcePolicy protocol (filter + can), ResourceTypeRegistry and the register_resource_types hook (app.state.sm.resource_types); OwnedResourcePolicy builds owner OR public(view) OR EXISTS(share) with an explicit bypass permission, so lists and paginate() counts are filtered in SQL; RequiresResource loads only through policy.filter: invisible -> 404, visible but not allowed -> 403, and is a token gate only with permission=. Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
do_orm_execute listener (simple_module_db.bulk_audit) records ORM bulk and Core INSERT/UPDATE/DELETE on audited models through the per-engine audit sink: ids prefetched with the statement's WHERE and tenant scope, per-row entries up to 500, else one '*' summary. sm_audit=False opts out; record_bulk_audit() for text() writes; audit_log setting refuse_unaudited_dml makes the opt-out raise. Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…ception (#541) SpaMount + ModuleBase.spa_mounts(), SM031 overlap diagnostic, nativePrefixes shared prop, native_link_prefixes host setting, startSpaLinkInterception({ exclude }). Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
# Conflicts: # CHANGELOG.md
…API check from #534) Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
# Conflicts: # CHANGELOG.md # host/client_app/app.tsx
…es (#526) can() lower-cased both sides while the SQL predicate compared the coerced id; on a string owner column a case-variant id passed one and not the other. Both now go through the same _coerce + ==. Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…exemption, private caching for gated mounts (#541) Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…e recorded (#557) Changes come from the statement's SET/VALUES targets resolved to mapped attributes (bound values through the execute params, column-named params as SQLAlchemy writes them); stray keys and excluded columns under Core keys no longer leak. is_deleted written as SQL, a truthy value or in executemany rows is bulk_soft_deleted, a falsy one bulk_restored; PK and soft-delete-flag writes are never skipped as bookkeeping, so strict mode refuses them. Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…atement values (#557) A statement writing is_deleted keeps its other columns in changes; executemany rows get soft-delete/restore per row (distinct summaries past the cap). Each executemany row is the statement's .values() resolved through that row plus its column-named params, so a write cannot look excluded-only and escape auditing or strict mode. Executemany no longer prefetches with unbound params. Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…ss (#526) sharing_resource_share (tenant-scoped, audited, unique incl. tenant_id, hard delete on revoke) with share_exists_for() for OwnedResourcePolicy; expiry is checked in SQL. Grant/revoke/list API validates the resource type against the registry, the granter's can("share") on the row and the grantee via the user directory. Admin page /admin/sharing (en + de). Migration f733c7938fd1: a new root and head labelled sharing. Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…ing only for UPDATE (#557) An INSERT setting only excluded columns or is_deleted was dropped as excluded-only bookkeeping, and so also escaped refuse_unaudited_dml. Inserts now always produce at least the summary row. Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…526) On a multi-tenant app grant() now requires the grantee to be a member of the active tenant and answers a non-member with the same 422 as an unknown id (no cross-tenant enumeration); share listings only carry directory labels for members. Membership is read through a new core seam, simple_module_core.tenancy.tenant_member_ids (app.state.tenant_members, published by tenants, using the caller's session); with no active tenant or no membership store nobody qualifies. Single-tenant behaviour is unchanged. Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
Resolve conflicts keeping both sides: hosting batch 1 (CORS, forwarded host, rate-limit registry, DB options, OpenAPI security/SM031, request hooks mixin, RequestRegistries) alongside batch 3 (resource types, SPA mounts/SM032, server-rendered head, bulk audit, docker add-worker). register_resource_types stays on ModuleBase (not a request hook) and is passed to run_module_registrations next to RequestRegistries. Split _default_api_version into _api_version.py to keep host_settings.py under the 300-line cap. Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…lt resource registry; scaffold app excludes SPA mounts Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
…nning fails open (#557) Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
# Conflicts: # CHANGELOG.md # CLAUDE.md # docs/database/migrations.md # host/tests/test_migration_graph.py
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Deploying simple-module-python with
|
| Latest commit: |
b5868a6
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://e9dbc8de.simple-module-python.pages.dev |
| Branch Preview URL: | https://feat-features-batch-3.simple-module-python.pages.dev |
CI forces Rich colour, so '--force' in the rendered usage error was split by escape codes. Run the CLI wide and colourless and normalise the output. Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Features batch 3: bulk-write audit, worker images, server-rendered head (step 1), SPA mounts, and resource-scoped access with a new
sharingmodule. Design:docs/superpowers/specs/2026-10-21-features-batch-3-design.md.What changes
simple_module_db.bulk_audit. For UPDATE or DELETE on an audited table, it prefetches the affected ids (same WHERE, same params, same tenant scope) and writes onebulk_updated/bulk_deleted/bulk_soft_deleted/bulk_restoredrow per id, up to a cap. Past the cap it writes one summary row.execution_options(sm_audit=False).text()writes go throughrecord_bulk_audit().refuse_unaudited_dml.smpy docker add-worker <name> --queues --base --apt --extras --moduleswrites a Python-only worker Dockerfile and a compose override, and includes the override from the compose file. Every input is strictly validated, and re-running is idempotent.simple_module_inertia.Head(title, description, canonical, robots, og, twitter, JSON-LD) can be passed toinertia.render(..., head=...)and is rendered into the HTML<head>, escaped.og:urllinks are built from the newHostSettings.public_base_url, never from the request'sHostheader.ModuleBase.spa_mounts()returningSpaMount(prefix, dist_dir, auth=...).index.htmlas the deep-link fallback for HTML requests, returns a 404 for a missing asset (never HTML), and marks hashed assets immutable.private.nativePrefixesshared prop andHostSettings.native_link_prefixesstop Inertia from intercepting links into the mount.ResourcePolicy(can/filter, kept in parity) and aResourceTypeRegistryfilled byregister_resource_types.RequiresResourcedependency: an invisible resource returns 404, one visible but not allowed returns 403.OwnedResourcePolicyhelper (owner, public visibility, shares).sharingmodule: tablesharing_resource_share, migrationf733c7938fd1, labelsharing. Grants are validated against the registry and the owning module'scan("share"), and tenant membership is checked through the coretenant_member_idsseam. Admin page included.The migration graph test pins 4 roots and 6 heads.
Verification (after merging main with batch 2)
make lint✓make test-py: 5566 passed ✓make test-js: 685 ✓make ci-doctor✓uv lock --check✓/ship
can/filterparity, and cross-tenant enumeration in sharing.Head()yet.Manual:
simple_module_sharingis a new PyPI package, so it needs a pending publisher registered before release.Closes #557
Closes #540
Closes #541
Closes #526
https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4