You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Epic: configurable Projects + per-tenant isolation, and the authz-core fixes
Audit of whether this service can back (a) a kids learning platform, (b) a working-professionals learning platform (launching this week), and (c) many SaaS integrations (email, Slack, Linear, incident.io, docs, slides). It can back the existing B2C/B2B membership surface today, but falls short of the roadmap. This epic tracks the foundational slice being implemented now; the rest are filed as #8–#25.
Verdict
The repo is a clean, correct Zanzibar-style authz core (union/computed/tuple_to_userset over project-sharded relation tuples, with memory+postgres drivers behind a conformance suite). For the existing B2C/B2B membership surface it is sufficient and ships safely today: workspaces, members, groups, invitations, and concrete-user Check/Expand all work and are data-isolated by project_id. But it is NOT sufficient for the broader product roadmap, and it has one genuine correctness/security defect that the pro platform must not ship around: SUSPENDED membership is cosmetic — a suspended member still passes every Check because the engine is strictly monotonic (no exclusion operator) and Check never consults membership status, so there is no safe way to revoke a member's live access without destroying their membership. That, plus the absence of any safe bulk deprovisioning path, is the only true P0 for a this-week pro launch.
Everything else is real but additive and sequenceable. The findings collapse into a small number of foundational engine/schema capabilities — a per-project configurable model + schema-write API, the intersection/exclusion rewrite operators, a wildcard/public subject, a condition/caveat (ABAC) layer, tuple TTL, userset-subject Check, ListObjects/BatchCheck, a consistency token, and an append-only audit/history layer — on top of which the product- and compliance-facing needs (kids COPPA consent/age-gating/erasure, docs link-sharing and nested folders, pro seats/roles/cohorts, integrations scoped delegation/principal mapping, residency, metrics, rate limiting) all build. I merged the ~40 raw findings into 23 issues: the same exclusion operator surfaced in 5 findings (engine-completeness, suspended members, kids restricted accounts, kids consent-withdrawal, docs "except Bob") becomes one engine issue plus consumers; wildcard surfaced in 5; conditions/caveats in 6; TTL in 5; audit in 4; ListObjects in 4; per-project model in 5. All changes are wire-additive (new proto field numbers, new RPCs, new optional config, NULL-defaulted columns, new namespaces), default behavior is preserved when new fields are unset, and existing stored tuples and callers are unaffected — consistent with the user's explicit backward-compat mandate and the repo's no-shims rule (we change the shape additively, not via translation layers).
Shipping now — branch feat/configurable-projects-tenancy
Identity parity (per identity ADR-0001/0002) plus the keystone engine fixes. All additive; existing callers and stored tuples are unaffected, default behavior preserved.
Per-project authorization model — model stored in project config, resolved per request with an in-process cache, falling back to DefaultModel() when unset (so every existing project behaves exactly as today). Lifts the single hardcoded DefaultModel.
Clean per-tenant data separation — tenant_id threaded alongside project_id through proto, both drivers, the engine reader, the service layer, and the conformance suite (new TenantIsolation test). Empty tenant_id ⇒ default tenant "" (no backfill, no behavior change).
Intersection + exclusion rewrite operators — root-cause fix for cosmetic SUSPENDED, "shared with team except Bob", consent-withdrawal denial, restricted accounts. (was #engine-intersection-exclusion-operators)
Every change here and in #8–#25 is additive (new proto field numbers, new RPCs, new optional config, NULL/empty-defaulted columns, new namespaces). No shims or translation layers (per AGENTS.md) — shapes change additively. Existing stored tuples and existing callers keep working with unchanged defaults.
Epic: configurable Projects + per-tenant isolation, and the authz-core fixes
Audit of whether this service can back (a) a kids learning platform, (b) a working-professionals learning platform (launching this week), and (c) many SaaS integrations (email, Slack, Linear, incident.io, docs, slides). It can back the existing B2C/B2B membership surface today, but falls short of the roadmap. This epic tracks the foundational slice being implemented now; the rest are filed as #8–#25.
Verdict
The repo is a clean, correct Zanzibar-style authz core (union/computed/tuple_to_userset over project-sharded relation tuples, with memory+postgres drivers behind a conformance suite). For the existing B2C/B2B membership surface it is sufficient and ships safely today: workspaces, members, groups, invitations, and concrete-user Check/Expand all work and are data-isolated by project_id. But it is NOT sufficient for the broader product roadmap, and it has one genuine correctness/security defect that the pro platform must not ship around: SUSPENDED membership is cosmetic — a suspended member still passes every Check because the engine is strictly monotonic (no exclusion operator) and Check never consults membership status, so there is no safe way to revoke a member's live access without destroying their membership. That, plus the absence of any safe bulk deprovisioning path, is the only true P0 for a this-week pro launch.
Everything else is real but additive and sequenceable. The findings collapse into a small number of foundational engine/schema capabilities — a per-project configurable model + schema-write API, the intersection/exclusion rewrite operators, a wildcard/public subject, a condition/caveat (ABAC) layer, tuple TTL, userset-subject Check, ListObjects/BatchCheck, a consistency token, and an append-only audit/history layer — on top of which the product- and compliance-facing needs (kids COPPA consent/age-gating/erasure, docs link-sharing and nested folders, pro seats/roles/cohorts, integrations scoped delegation/principal mapping, residency, metrics, rate limiting) all build. I merged the ~40 raw findings into 23 issues: the same exclusion operator surfaced in 5 findings (engine-completeness, suspended members, kids restricted accounts, kids consent-withdrawal, docs "except Bob") becomes one engine issue plus consumers; wildcard surfaced in 5; conditions/caveats in 6; TTL in 5; audit in 4; ListObjects in 4; per-project model in 5. All changes are wire-additive (new proto field numbers, new RPCs, new optional config, NULL-defaulted columns, new namespaces), default behavior is preserved when new fields are unset, and existing stored tuples and callers are unaffected — consistent with the user's explicit backward-compat mandate and the repo's no-shims rule (we change the shape additively, not via translation layers).
Shipping now — branch
feat/configurable-projects-tenancyIdentity parity (per identity ADR-0001/0002) plus the keystone engine fixes. All additive; existing callers and stored tuples are unaffected, default behavior preserved.
Project—Project{id,name,status,config_json,...},Admin{Create,Get,Update,List}ProjectRPCs gated byGATEWAY_ADMIN_API_SECRET, idempotentensureDefaultProjectbootstrap. (was #per-project-configurable-model)DefaultModel()when unset (so every existing project behaves exactly as today). Lifts the single hardcodedDefaultModel.tenant_idthreaded alongsideproject_idthrough proto, both drivers, the engine reader, the service layer, and the conformance suite (new TenantIsolation test). Emptytenant_id⇒ default tenant""(no backfill, no behavior change).SUSPENDED, "shared with team except Bob", consent-withdrawal denial, restricted accounts. (was #engine-intersection-exclusion-operators)user:*) — link-sharing / published-course visibility. (was #wildcard-public-subject)ListObjectsreverse query — "what can this learner see?" (correctness-first; reverse-index perf is a follow-up). (was #listobjects-reverse-index)SuspendMember/ReinstateMember(deny by tuple absence) + atomic userset-awareDeprovisionUser. (was #safe-suspend-deprovision)Deferred — filed as backward-compatible issues
Attribute & time aware: #8 conditions/caveats (ABAC: parental consent, age-gating, scoped delegation) · #9 tuple expiry/TTL
Subject kinds & scale: #10 userset-subject Check · #11 BatchCheck
Accountability (SOC2/COPPA/GDPR): #12 tuple-change history · #13 decision/audit log · #14 subject erasure & export
Consistency: #15 zookie/consistency token
Pro platform: #16 seat/license enforcement · #17 product-defined domain roles (instructor/learner/TA) · #18 cohort/enrollment lifecycle · #22 ownership transfer
Integrations: #19 scoped delegation / on-behalf-of · #20 service-credential→principal mapping
Docs/Slides: #21 nested-folder inheritance
Operability: #23 decision metrics · #24 per-tenant rate limiting · #25 data-residency routing
Backward-compatibility mandate
Every change here and in #8–#25 is additive (new proto field numbers, new RPCs, new optional config, NULL/empty-defaulted columns, new namespaces). No shims or translation layers (per AGENTS.md) — shapes change additively. Existing stored tuples and existing callers keep working with unchanged defaults.