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.
Tests first, on SQLite and PostgreSQL: the registration-time lifetime cap in both modes (with a released-SDK-shaped root accepted), record versus enforce for roots that outlive their authority, the advertised mode in GET /info, legacy long roots counted, and the current rules for unclassified registrations after the cutover.
Registration refuses a root unless exp <= registration + 31 days (+5 min) and nbf <= exp, in both modes. policy_v3.owner_authority_expiry selects record (default: roots outliving their authority are accepted and counted) or enforce (refused). Unclassified registrations after the cutover get the current rules; earlier ones are counted, never capped. GET /info advertises policy-owner-authority-v1 and the active mode. Adds the owner-authority counters and the counting queries.
…ty-record' into skgbafa/tc-1423-n2-root-lifetime-cap
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.
…ty-record' into skgbafa/tc-1423-n2-root-lifetime-cap # Conflicts: # CHANGELOG.md # tinycloud-node-server/src/policy_v3/owner_authority.rs
Tests first, on SQLite and PostgreSQL: revoking the owner's grant, an agent session (did:pkh wallet -> agent key), an ancestor or an alias-CID encoding ends already-admitted sessions across read, decrypt, re-shared children, challenge, mint, signed-KV mint and re-share admission with a fixed code; all-of grants; typed expiry codes; legacy registrations denied without authority unless escaped and never expiry-capped; hook streams closing after a grant is revoked or after repeated check errors; the 16-authority ticket cap. The delegated-session flow now expects the admitted session to stop when the owner's delegation is revoked.
… (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.
…ty-record' into skgbafa/tc-1423-n2-root-lifetime-cap
Unit tests pin the root-lifetime cap at exactly registration + 31 days + 5 minutes and the authority_ttl_bucket edges (1 hour, 30 days). The bucket takes the authority expiry itself (an uncapped root's authority always expires; the unreachable 'none' bucket is gone). The boot log line carries the active owner_authority_expiry with the cutover. Docs: the counters are exported on the Prometheus port (telemetry.enabled; else the counting queries), the cap is described as an expiry bound, and re-posting an existing registration that breaks the current rules gets the same 403.
Every policy use checks the recorded owner authority after the root checks: reads, decrypts and re-shared children of admitted sessions, re-share admission, signed-KV and hook-ticket mints, challenge, mint (and its locked recheck), S0 admission and delivery. A revoked delegation or ancestor (also under an alias CID) gives policy-owner-authority-revoked; a non-legacy bound authority past expiry policy-owner-authority-expired; no authority policy-owner-authority-unknown, unless legacy and policy_v3.legacy_unknown_allow. The mint's registration-time rescan runs only for escaped policies. Expired roots and sessions get typed codes. Hook tickets carry a policy session's closure (at most 16 IDs) and open streams re-check their authority, closing on a revocation or three failed checks; the chain check is two queries at any depth. What to check is cached once final; liveness never is.
… 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.
…-cap' into skgbafa/tc-1423-n3-owner-authority-liveness # Conflicts: # CHANGELOG.md # tinycloud-node-server/src/policy_v3/owner_authority.rs
…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.
…-cap' into skgbafa/tc-1423-n3-owner-authority-liveness
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.
…ty-record' into skgbafa/tc-1423-n2-root-lifetime-cap
The re-share fixture signs the child from the next whole second after the session starts (PostgreSQL keeps microseconds); it now waits for that second before importing, instead of racing it.
…-cap' into skgbafa/tc-1423-n3-owner-authority-liveness
- Hook streams: after a failed authority check, the next check waits the interval; events are withheld meanwhile without querying, so a burst during a brief database error no longer closes a healthy stream. - First-use classification is single-flight per policy: concurrent uses share one computation, and a use arriving just after it reuses the in-memory result. - Tests: the revoked-stream test matches rocket's `event:write`, has a positive control and no idle re-check, so the pre-event re-check is what closes it; S0 re-import, delivery, the escape's legacy-only scope and the escaped mint's rescan each get a test; concurrent first uses count one classification. The policy flow tests box their future. - CHANGELOG: delivery keeps `delivery-authorization-invalid`. Docs note that `policy-owner-authority-expired` is not reached by stored rows.
added 17 commits
October 24, 2026 01:56
…n2-root-lifetime-cap
…ner-authority-liveness
…n2-root-lifetime-cap
…ner-authority-liveness
…e the harness network; update rollback runbook
…n2-root-lifetime-cap
…ner-authority-liveness
…t-after-revoke check and the use-status check; neutral wording
…0-issuer-registration
This was referenced Oct 24, 2026
added 9 commits
October 24, 2026 10:49
…n2-root-lifetime-cap
…ner-authority-liveness
…0-issuer-registration
Contributor
Author
|
APPROVE: see TC-1530 (reviewed exact head ca24f6d) |
This branch has not been deployed
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.
Summary
issuerKid) is not admitted by the configured issuer set for the requirement'svctand profile.vctand the permitted issuers fortinycloud.email-held/v1,tinycloud.org-membership/v1andtinycloud.org-membership-free/v1. A configured entry whoseissuerDidviolates a row named in itsprofilesmakes the whole set invalid at load.Scope
(issuerDid, kid?),vctandprofileagainst the set. Validity windows (notBefore,retiredAt,notAfter) are enforced at verification, not at registration.Order of checks
owner_holds_capabilitiesand before any stored registration row is read.Spec
did:web:issuer.credentials.org; the org rows permitdid:web:o+ 26[a-z2-7]+.orgs.credentials.org.issuerDidmatches the permitted issuers of every row named in itsprofiles; one entry that does not makes the whole set invalid. A policy namesissuerDids and may pin akid; it cannot add an issuer the verifier's set lacks.Refusal
403 credential-issuer-untrustedwhen the requirement's issuer is not admitted for its(vct, profile)by the configured set.tinycloud.email-proof/v1,tinycloud.email-domain-proof/v1) are not row-checked by this change.Tests
policy_registration_refuses_an_issuer_outside_the_configured_set: full registration route with a configured set that omits the policy's issuer; asserts403 credential-issuer-untrustedand that no registration or root row is written. The flow wrappers box the future (Box::pin) and take anissuer_trustedargument; the source-network tests passtrue.owner_authority_is_checked_before_the_issuer_set: an unauthorised owner naming an untrusted issuer gets the owner refusal.registration_membership_requires_the_row_vct_for_the_profile: an entry listing both vcts is refused for the crossed(vct, profile)pair.registration_membership_follows_did_kid_vct_and_profile: pinned-kidmismatch cases for the org vct and org profile.registration_admits_a_free_row_requirement_the_entry_lists: current behaviour for the free-row profile. Refusal of the free-row requirement belongs to the list-admission work.held_row_permits_matches_the_spec_parity_fixture: 72(profile, issuerDid)pairs intestdata/held_row_parity.tsv. Expected verdicts come from the section 3.3 rules. This is a fixed set of cases, not an exhaustive comparison.held_row_violation_invalidates_the_whole_set: load-time rejection of a row-violating entry.Stack
This branch contains the head of #362 (merged in), so that the policy flow wrappers in
policy_v3.rstake both the source-network tests and theissuer_trustedargument. Merge order: #363, #358, #360, #362, #396, #399, #392.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, except the threestartup_identitycases (the host has a dstack socket, so the node binary refuses to create an identity) 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-1530