Specifications / Packages and automation
Organization Package Governance
Status: normative model and DataProvider structural migration; hosted permission endpoints and mutation policies remain delivery work. This refines names and ownership, deployment security and rollout. The canonical typeDiagram is package-registry.td; the database source is DataProvider schema.yaml. Neither a diagram nor an applied structural migration proves working authenticated publication.
Prevent name sitting [PACKAGE-NAMES-NO-SITTING]
Preventing package-name sitting is a primary design goal. A package leaf name is not a globally ownable resource. Publishing or reserving @org-a/helper-functions MUST have no effect on @org-b/helper-functions, now or later. There is no global name-registration endpoint, first-come claim, reservation ledger, alias, search promotion right or exclusivity check for helper-functions alone. An organization is responsible for its fully qualified packages; a person cannot acquire a portable monopoly over a useful word by arriving first.
Every uniqueness query, availability check, reservation, publication, moderation action and tombstone MUST use the owning organization plus package name. “Already taken” means taken within this organization; the website must say so and must never report the leaf name globally unavailable. Search can find matching names across organizations and must display the organization beside every result. Security action against one organization's package must not reserve or silently disable that name in other organizations. Organization enrollment proof and reservation quotas address abuse of the organization namespace separately; they must not reintroduce a global package-name market.
Organization identity [PACKAGE-OWNERSHIP-MODEL]
Every hosted package MUST belong to exactly one registry organization. A user creates an organization or accepts membership in an existing one before creating or administering any package. There are no personal scopes, personally owned packages, unowned packages, global package names or implicit default organizations. A one-member organization is valid but has the same governance, recovery and accountability requirements as a larger organization.
The existing canonical spelling stays @organization/package: for example @my-org/helper-functions. HelperFunctions may be a display title only; it is not a package identifier. helper-functions, my-org/helper-functions, inferred organizations and account-name fallback are rejected by resolution, publication, dependency declarations and locks. Both @my-org/helper-functions and @another-org/helper-functions may exist; nobody can reserve helper-functions across organizations. Organization handles are unique solely to route to an accountable organization, not to create a global package-name market.
PackageKey.scope means the owning organization's immutable canonical handle, not a username. Preserve the current ASCII lowercase/digit/interior-hyphen grammar and lengths in [PACKAGE-NAMES]; do not introduce alternate case-folding or URL-decoding aliases. An organization's public handle, immutable organization ID and verified provider bindings are distinct. Matching a GitHub org name is not verification. Enrollment proves a stable GitHub org ID/admin assertion when claiming GitHub affiliation, plus the registry creator's credential. Unlinked registry organizations are clearly labelled. Never use emails, mutable GitHub handles or display names as authority.
The database uniqueness key is (organization_id, name), never name. (organization_id, id) keys support all tenant foreign keys. Tombstones retain handles and package keys permanently; an empty/draft/tombstoned package cannot advertise itself as a maintained release. Organization registration is rate-limited and proof-backed; reserved official handles and misleading affiliation are rejected. Organization and draft-package quotas limit bulk reservations. Those limits do not let one org block another org's same-named package.
Organization governance belongs to accepted members marked custodian; ordinary members are member. These are organization governance roles, not package administrator roles. Multiple custodians may exist for continuity and quorum, but custodianship never grants package permissions. Creation records the organization and its first accepted custodian atomically. Granting custodianship, changing recovery authority or organizational control requires two existing custodians' fresh signatures; a sole-custodian organization uses that custodian plus an independent pre-enrolled recovery principal and a logged hold. A proposed recipient cannot supply the independent approval. No organization can lose its last accepted custodian.
Exactly one package administrator [PACKAGE-OWNERSHIP-ADMIN]
Every package has one non-null admin_membership_id, referencing an active membership in its owning organization. The package row is the sole current administrator assignment. There is no admin value in the grantable permission enum, no administrator list and no organization role that implicitly becomes package administrator. One person may administer several packages, and several people may publish one package; each package still has one administrator.
Enforce this structurally: packages.admin_membership_state is constrained to active and admin_principal_kind to human; the composite foreign key (organization_id, admin_membership_id, admin_membership_state, admin_principal_kind) references memberships(organization_id, id, state, principal_kind). Membership principal kind is itself foreign-key bound to the principal's actual kind. Null admins, workflow admins, administrators from another org and revocation of a membership still administering a package fail in PostgreSQL. The same human-kind constraints protect custodians, handover recipients and recovery authorities. This avoids circular foreign keys: memberships belong to organizations, not packages. The API must not expose organization IDs, administrator IDs, membership states or authority epochs through generic PATCH.
The administrator alone decides ordinary package permission grants, revocations, renewals and expiration. An administrator has authority to manage permissions and initiate their own handover, but needs an explicit publish-release or other operational grant to perform that operation. Initial package creation records the chosen active member as the single administrator; any initial publishing grants are explicit audited decisions accepted by that administrator. An organization custodian may create a package, but cannot install themselves as administrator without the nominee's fresh credential-backed acceptance.
A normal administrator handover requires the current administrator's fresh signature, the proposed active human org member's fresh acceptance, the exact current package authority epoch, notifications and a 24-hour pending hold. A pending transfer is only a proposal: the old admin remains the sole admin until one atomic update replaces the reference and increments the epoch. Only one pending transfer per package is allowed by a partial unique index. Cancelled, declined, expired and completed proposals remain evidence. Completion's recorded decision time must lie between not_before and expires_at; handlers independently verify trusted current time and cannot backdate a completion. A stale proposal cannot replace a newer admin; the expected-admin and epoch comparison must both match. No period with zero or two administrators is permitted, including during retries or worker crashes.
On handover, revoke all existing package grants and delegated workflow credentials and invalidate queued intents/challenges, including grants to the outgoing admin; the new admin explicitly approves the new permission set. Never inherit an outgoing administrator's permission decisions silently. Existing signed releases and responsible organization identity remain unchanged.
If the administrator is suspended, disappears or refuses cooperation, freeze package writes immediately. Custodians cannot mint a second admin or use a direct database update. Exceptional replacement uses the existing 30-day recovery quarantine and two-of-three independent registry-security-custodian approval from [PACKAGE-NAMES], recipient acceptance, org-governance evidence and a logged atomic replacement. The recorded admin reference stays singular while frozen. Removing/revoking an administrator membership requires completing normal handover or exceptional replacement first; a principal-level suspension provides immediate denial without breaking the FK.
Permission model [PACKAGE-OWNERSHIP-PERMISSIONS]
Permissions are explicit per-package capabilities granted to accepted organization memberships. Several members can hold the same permission simultaneously. Each row contains exactly one permission; there are no role bundles, wildcards, deny/allow precedence rules, cross-org subjects, automatic grants from GitHub membership or transitive delegation.
| Permission | Allows | Does not allow |
|---|---|---|
publish-release |
Submit intent and final authorization for this package, including a pinned approved CI workflow | Manage permissions, change repository binding, replace source bytes or bypass any trust gate |
yank-release |
Request a signed yank for this package under the independent status threshold | Revoke security status, delete history or administer grants |
edit-metadata |
Edit mutable summary/links/discovery presentation with audit evidence | Change package key, org, repository binding, release manifest/digests or verification status |
manage-maintenance |
Acknowledge reports, record maintenance decisions, deprecate/request succession and publish maintenance notices | Appoint an admin, transfer ownership or claim security assurance without evidence |
manage-security |
Access restricted security reports, triage and submit signed advisory evidence | Suppress independent security findings or single-handedly mark a release safe/revoked |
Public catalog/source reads require no permission. Active organization members can see the non-secret organizational package inventory and maintenance responsibility; own grants/invitations are visible only to their subject and the authorized control path. Restricted report content needs manage-security, or an explicitly authorized independent incident role. Being the administrator does not grant access to confidential reports or operational permissions implicitly; the admin can explicitly grant themselves a capability with the same audit/proof process. Public views never expose membership lists, private reports, OAuth subjects or provider tokens.
Each grant records package/org/member IDs, permission, current grant state, grantor membership, package authority epoch, creation time and optional expiry. The live authorization predicate is: active principal/session AND active organization/member/package AND this exact package AND active, unexpired permission AND matching authority epoch. Supplied scope/user/role text is never proof. The administrator can revoke a grant or all grants immediately; permission holders cannot delegate, renew, widen or transfer their own grants. Organization removal invalidates every grant even when JWTs remain valid. Revoked/expired grants remain auditable and cannot be resurrected by rejoining; issue a fresh decision.
Only active memberships are unique per (organization_id, principal_id); rejoining creates a new membership ID and never reactivates the departed one. Only active permission rows are unique per package/member/permission; renewal atomically revokes the previous row and inserts a newly signed decision with a new ID. An expired row still marked active must be explicitly retired in that same transaction. Membership authority_epoch increments on each role/status authority change and is captured alongside the package epoch by pending control/publication operations; workers reject either mismatch. The historical rows and grantor/creation facts remain immutable.
CI uses a distinct verified workflow principal enrolled as an organization member, with explicit package permission and exact repository/workflow/ref/issuer binding. Its grant requires a responsible human sponsor, expires and is independently revocable. A workflow principal cannot become package administrator/custodian/recovery authority. Humans sign publication through WebAuthn and workflows through the existing ephemeral DSSE mechanism; database grants and API tokens never replace those signatures.
Responsibility and maintenance [PACKAGE-OWNERSHIP-MAINTENANCE]
The organization remains publicly responsible through administrator changes, contributor departure, permission expiration and incident response. Show organization identity, current maintenance status, last evidence-backed review and public security advisories beside each public package. A package listing that cannot surface known security warnings must remain hidden, consistent with repository policy. A mutable maintainer display must not rewrite who authorized a historical release.
Maintenance reports are scoped to a package and organization, carry an immutable reporter, evidence digest, creation time and explicit state (open, acknowledged, resolved, withdrawn). Reports/decisions enter the audit stream. manage-maintenance may publish a response or succession request, but cannot erase reports or mark independently recorded security findings resolved. No activity is fabricated because a package has a new admin. Maintenance scoring and independent appeals remain governed by spec 0032.
Package lifecycle is explicit: draft, active, write-frozen, deprecated, tombstoned. Deprecation leaves old releases verifiable, with visible replacement guidance; a replacement always names another complete canonical key. Tombstoning never frees the key or deletes release/advisory history. Public availability and client acceptance continue to obey the signed Eligible/Yanked/Revoked protocol, not this mutable maintenance projection.
Changing administrator is not changing owning organization. Moving responsibility to another organization creates a new fully qualified key, with a signed old/new organization transition, 30-day public notice and logged migration pointer under [PACKAGE-NAMES]. Never UPDATE a package's organization ID/name or silently redirect dependency resolution. Old locks still identify the original organization and exact release. Organization dissolution freezes packages and triggers the same explicit succession path; no automatic personal fallback, registry takeover or handle recycling.
Database contract [PACKAGE-OWNERSHIP-DATABASE]
Provision the private registry PostgreSQL schema as a platform resource before table migration. Use DataProviderMigrate and only checked-in declarative YAML for these tables; do not introduce handwritten SQL migrations, ad-hoc bootstrap scripts or a parallel ORM schema. DataProvider is an explicitly requested deployment dependency, not a C# package-service implementation. Its runner may apply the schema; application authorization, proof verification, state machines and tests remain Osprey. Pin the deployment tool independently of the package runtime. Missing DataProvider features are migration blockers, not justification to weaken integrity. The installed 0.9.11-beta CLI currently applies these custom-schema tables but inspects public during integrity verification, so the migration returns failure and convergence is blocked. This failure is retained in the rollout plan; no hosted deployment may treat the structural probe as a successful migration. The CLI also logs its connection argument: credentials must use a mounted passfile/provider secret mechanism, never a password in that argument.
Table in private registry schema |
Purpose and critical constraint |
|---|---|
principals |
Stable human/workflow identity; active/suspended state; no personal package ownership |
auth_bindings |
Unique (issuer, provider, provider_subject) → principal; auth subjects never accepted as org IDs |
organizations |
Stable organization ID, unique permanent canonical handle, state and governance epoch |
memberships |
Unique active (organization_id, principal_id); custodian/member role, active/revoked state; composite active-membership reference target |
recovery_authorities |
Accepted, proof-backed independent human recovery principals; unique active org/principal binding and governance epoch; no workflow authority |
packages |
Non-null org and single admin reference; unique (organization_id, name); immutable identity, state and authority epoch |
package_permissions |
Unique active (organization_id, package_id, membership_id, permission); same-org composite FKs for subject and grantor; no admin permission |
admin_transfers |
Current/proposed same-org admins, active-recipient composite FK, expected authority epoch, one pending proposal per package, signed-proof digests, expiry, acceptance and decision time |
invitations |
Exact target principal/org, member/custodian role, issuer, hashed one-use token, expiry and pending/accepted/revoked/expired state; acceptance consumes the token and role approval rules still apply |
maintenance_reports |
Same-org package/reporter references, public summary, bounded evidence digest and explicit report state; private security evidence stays separate |
governance_events |
Append-only org/package/principal context, operation/epoch/proof commitment and previous-event digest; no PII in public event projections |
All identifiers are UUIDs except canonical handle/name components. Cross-org composite FKs are Restrict/NoAction, never cascaded ownership deletion. Unique reservations include tombstones. State/permission/role/epoch/expiry and name-format checks are database constraints. Audit append-only permissions, immutable-column grants, live membership policies and atomic transition checks must additionally pass runtime-role tests; a foreign key cannot verify signatures or enforce a transition history.
Recovery enrollments bind existing human principals and include their accepted signed proof. External registry-security custodians remain pinned by the independent trust policy, not self-appointed application rows. Multi-custodian approvals, handover decisions and maintenance/deprecation decisions are signed canonical evidence in CAS; governance_events.proof_digest commits to the complete proof set, including actor, outcome, replacement PackageKey when present and time. Notifications and public views project that verified evidence. Event appending must serialize the organization head and reject multiple genesis events/forks; the nullable predecessor column alone does not enforce a single chain. These are explicit Osprey transition gates before opening bootstrap RLS.
The initial structural migration deliberately enables and forces RLS with zero allow policies and no runtime grants on every table. It is a fail-closed bootstrap, not a claim that hosted flows work. It must stay closed until Osprey transition handlers, narrow role/column grants, session context, nonrecursive row policies and adversarial tests ship together. Do not copy TradiSite's generic app_admin, USING (true), owner login or session-scoped identity context. No exposed Supabase Data/RPC/Realtime route may bypass the API. A deployment encountering the bootstrap-only policy state must report ownership.not-ready and refuse write readiness rather than showing empty data as a working feature.
Runtime enforcement must independently distinguish package-admin control operations from grant-holder operations. INSERT/UPDATE of a permission row requires the current admin, same-org subject, exact current authority epoch and matching signed decision; the ordinary subject role cannot touch grant/epoch/grantor columns. Only the handover/recovery control path may update the single admin column after the specified proofs/hold; it cannot change org/name. Ownership changes, membership removal, grant changes and publication finalization serialize on the same org → package lock order. Every queued worker rechecks authority before collection, signing and eligibility. Reject old epochs after handover/removal; no pending job can make itself current by updating its recorded epoch.
Acceptance [PACKAGE-OWNERSHIP-CONFORMANCE]
- Prove two organizations can use the same package leaf name and each can have several publishers. Register, reserve, tombstone and moderate the first organization's key, then prove the second organization's same-named package remains available and publishable. Assert no global leaf-name unique constraint, availability query, reserved-name list or registration endpoint exists. Reject global/unscoped names, personal ownership, another org's admin/member/grant, null admin,
admin/wildcard permission and expired grants. - Prove exactly one admin exists before/during/after handover, concurrent acceptance, cancellation, retry, suspension and recovery. Reject a second pending transfer, stale admin/epoch, self-issued permission escalation and revocation/deletion of the referenced admin membership.
- Prove org custodians/members and a publisher cannot administer permissions without being the current package admin. Prove the admin cannot publish/yank/read restricted reports without an explicit corresponding grant. Package creation needs an accepted admin, not just membership.
- Prove handover invalidates old grants, workflows and queued authorizations; departure/rejoin does not revive grants. A change of admin preserves the owning organization, package key, old releases, reports and security history.
- Apply the actual DataProvider YAML to disposable PostgreSQL; verify all eleven tables, constraints, forced RLS, default denial, composite FKs and convergence on a second migration. SQLite emulation and owner-only tests are insufficient security evidence.
- Before hosted writes, run positive and negative Osprey tests as actual runtime roles for every table/operation, direct Supabase access path and pool-reuse/error/cancellation case. An applied bootstrap schema is not permission-test completion.
The rollout must not mark ownership complete until these tests and the wider trust/hosted conformance suite pass.