Repository navigation
Conversation
…tions Tests first, on SQLite and PostgreSQL through the production app: what a registration records, the owner-first and loop-back selection rules, first-use classification of earlier registrations, the opt-in sweep, rollback and the legacy cutover, and listing by a closure member.
Registration selects, per root capability, the owner delegation that grants it (root authority; then delegations from the owner of the capability's space or network; then the general window), skipping candidates whose closure includes a delegation issued by the policy owner and preferring one that outlives the root, and stores its ancestor closure in two new tables. Earlier registrations are classified on first use, claim-first, or by an opt-in sweep. POST /policy/v3/authorized lists policies by a closure member. Admission is unchanged.
|
Review at Verified locally on SQLite and a native PostgreSQL 14:
Findings:
Details: TC-1423. |
Policy reads, challenges and mints no longer wait on owner-authority classification: final classifications are remembered in process, other policies are classified by one background task each, and an attempt that doesn't end final (a failed write, starved) is not retried for 60 s. The general window only holds delegations touching the capability's space or network, loop-back candidates don't use up its tries, and starved is re-evaluated instead of final (classifier version 2). Listing errors return a fixed body; the default cutover is logged at warn while it is under a day old. Adds tests for SIWE grants issued after registration, SIWE floods, owner-derived floods and a window full of skipped rows.
|
Delta review at The previous findings are fixed in code:
Local reruns on SQLite and native PostgreSQL 14:
Remaining:
Details: TC-1423. |
… (RED) Reproduction from the N1b design: a policy owner holding only kv/get and kv/metadata on the content registers a Policy/v3 whose source network is named after itself and was never created. Today registration is accepted.
…RED) Foreign, missing, inactive, chain-mismatched and self-named networks, every ceiling network, the legacy source form, and the Share/D3 did:pkh shapes over the real /delegate, /encryption/networks and registration routes. Adds the refusal counter the tests read.
At most three owner-authority classifications run in the background at once; a use that finds no free slot starts none and doesn't back off. A classification task releases its policy and slot when dropped, including on panic. Tests: one window for a grant and its derived delegations (a child may not outlive its parent across a second boundary), a fixed high-id choice for the window-ordering fixtures, loop-back rows not using up tries, starved re-evaluated in a later process, the slot limit and the panic release.
… at registration
Every encryption network a new Policy/v3 names (the content source's and each
encryption ceiling entry) must parse, belong to the owner of the content
source's space (principal match: EIP-55 case-insensitive, chain id exact) and
be active on this node; otherwise registration returns
403 content-source-network-invalid. New registrations with a legacy
tinycloud:// kvResource are refused the same way. Refusals are counted in
tinycloud_policy_source_network_refused_total{reason}.
The Policy/v2 flow fixtures now create the content owner's network they
register against.
…istence lookup content_owner_networks (parse, owner, legacy form) is a pure function so stored registrations can be classified with the same rule; source_network_refusal adds the existence and state lookup for new registrations, after every network has passed the owner check.
|
Delta review at Verified locally on SQLite and native PostgreSQL 14:
Remaining:
Details: TC-1423. |
From the #363 review (B1): a kv-only key moves the content source to its own space on a network it created, and adds a second kv ceiling entry on the space owner's content. Registration is accepted at 1bfa6cf. Regression tests in the Policy/v2 flow and the registration harness, plus controls: the key sharing its own space, several kv entries all in the owner's spaces, and legacy or unparsable kv entries.
… owner Review B1 on #363: the network check bound the networks to the owner of contentSource.kvResource only, so a kv-only key could move the source to its own space on its own network and add the space owner's content as a second kv entry. content_owner_networks now also requires every kv ceiling entry to be non-legacy, to parse, and to name a space of the content source's owner (refusal reason foreign_space); a ceiling entry of any other kind fails it. The predicate stays pure, so N1's classifier reuses it unchanged.
The general-window loop-back test chose its target id from one signed delegation, so on rare runs no fresh delegation sorted before it. Every window-ordering fixture now picks its target with high_id.
|
APPROVE: see TC-1423 |
# Conflicts: # CHANGELOG.md # tinycloud-node-server/src/policy_v3.rs
Main gated load_closure_edges to cfg(test). The PR's non-test policy_owner_authority::load_closure calls it for raw parent edges, so the merged non-test build failed (E0425). Ungate it; production locking still uses load_chain_lock_ids.
…ner-authority-record # Conflicts: # tinycloud-core/src/migrations/mod.rs # tinycloud-node-server/src/config.rs # tinycloud-node-server/src/lib.rs # tinycloud-node-server/src/policy_v3.rs
…c-1423-n1-owner-authority-record
…tore and policy_v3 sections
…e the harness network; update rollback runbook
|
APPROVE: see TC-1423 (reviewed exact head 4df9331) |
Linear: TC-1423 (part 1 of 3)
Policy/v3 registration records which of the owner's delegations granted what the policy roots carry, together with that delegation's ancestor closure. #360 adds the root-lifetime cap and the record/enforce mode. #362 checks the record whenever a policy is used.
Changes
m20261019_000000_policy_v3_owner_authority. Adds two new tables,policy_v3_owner_bindingandpolicy_v3_owner_authority(policy_cid,delegation_id,depth), and an index on(delegation_id, policy_cid). There is no ALTER and no foreign key, and both tables are created empty.downdrops both tables. The migration is the last entry ofMigrator::migrations().policy_v3/owner_authority.rs):abilitiessubquery), at most 32 tried.bound,bound-uncappedorroot-authority.authorize_rootssees them. Nothing is added to the request: policies whose stored classification is final are remembered in process (bounded); others get one background task per policy, at most three at once. The classification runs as ofregistered_at(only grants issued no later than it). One transaction inserts the binding row first and the closure after it; if another writer stored a row first, its row is used. A result that is not final (a failed write, orstarved) holds off the next attempt for 60 s.unknownrows store a reason (revoked,missing,starvedorforeign_network).starvedis re-evaluated on later uses; the others are re-evaluated whenCLASSIFIER_VERSIONis bumped.content_owner_networks(from Check the owner and existence of policy source networks at registration #363) to the stored policy before it looks for delegations. A policy that names an encryption network, or akvspace, that does not belong to the owner of its content source is classifiedunknownwith reasonforeign_network, in both modes, whatever delegations the owner holds. The rule is a pure check of the stored policy; it does not look up whether the network exists.foreign_networkis final. No schema change.policy_v3.owner_binding_backfill.enabled, default off, paced bypolicies_per_second(default 1).policy_v3.legacy_cutover. Only registrations before the cutover count as legacy. It defaults to this migration'sseaql_migrations.applied_at. The effective value and its source are logged at boot.POST /policy/v3/authorized {cid, cursor}lists the policies whose recorded closure containscidor one of its alias CIDs.policyCid,policyRootCid,enforcementRootCid,registeredAt,expiresAt,rootStateandownerAuthorityState. Policy bytes are never returned./delegation/statusrule onurn:cid:<cid>; everyone else gets 403.policy_cid, with an opaque cursor and no total count. Unclassified legacy policies are not listed. Database errors return503 policy-listing-unavailable.tinycloud_core::policy_owner_authorityadds a one-round-trip closure loader (auth_graph::load_closure_edges), batched alias-aware revoked IDs (revocation::effective_rows), alias equivalents and the migration applied-at lookup.revocation.rsis not edited.docs/policy-owner-authority.mdand a CHANGELOG entry. The rollback section carries the SQL statements and a checklist, including thepolicy_v3.legacy_cutoverstep for the re-upgrade.Stack and merge order
main. This branch contains Check the owner and existence of policy source networks at registration #363 (merged in), because the classifier uses itscontent_owner_networks; Check the owner and existence of policy source networks at registration #363 merges first.owner_authority_testscreate the content owner'smainnetnetwork afterboot_app, since registration requires the source network to exist (Check the owner and existence of policy source networks at registration #363).### Changesheading under[Unreleased]with all pending bullets.Rollback
Run the three statements in
docs/policy-owner-authority.md(DROP TABLE IF EXISTSon both tables andDELETE FROM seaql_migrationsfor this migration), then deploy 1.22.0. On the re-upgrade, setpolicy_v3.legacy_cutoverto this release's first deploy time. The checklist in the document has one step for each.Tests
policy_v3/owner_authority_tests.rsboots the production app (app_with_control). Each test runs once on SQLite (file DB) and once on PostgreSQL (own schema,TINYCLOUD_TEST_POSTGRES_URL; PostgreSQL runs are CI-only).starvedis not final; a window full of skipped rows isstarved;owner_authority.rs(in-memory SQLite):unknown/foreign_networkinbackfillandrecordmode, and a policy over the content owner's network goes on to the delegation search (stored_registrations_over_foreign_networks_are_unknown);foreign_networkis read back as a final classification, and everyUnknownReasonround-trips;content_owner_networkscheck failsstored_registrations_over_foreign_networks_are_unknown.Verification
At this head, on SQLite:
cargo fmt --all -- --check;cargo clippy --workspace --all-targetsandcargo clippy -p tinycloud-core --features duckdb --all-targets, both withRUSTFLAGS=-Dwarnings;node scripts/gen-capabilities.mjs --check; and every test binary oftinycloud-core,tinycloud-nodeandtinycloud-authpass. Two sets of cases did not pass in the combined run: the threestartup_identitycases, which start the node binary and refuse to create an identity because the host has a dstack socket, and the threeaccount_alias_tests, which time out when other jobs share the host and pass when thetinycloud-nodelib tests run alone. PostgreSQL-backed tests run in CI.Details: TC-1423