Skip to content

FSD-004 post-fold surface (rc5 authority triad) + registry-core fold build + PortalService GetUser authz - #141

Merged
emooreatx merged 7 commits into
mainfrom
claude/substrate-upgrade-server-fold-ks4wtz
Sep 28, 2026
Merged

emooreatx merged 7 commits into
mainfrom
claude/substrate-upgrade-server-fold-ks4wtz

Conversation

@emooreatx

Copy link
Copy Markdown
Contributor

What & why

Three things, in three commits, all toward folding ciris-registry-core into CIRISServer.

1. Security fix (own commit): PortalService.GetUser / GetUserByEmail had no authorization. Any valid JWT could read any user record and all of that user's org memberships. Both RPCs now require the caller to be the user, a SYSTEM_ADMIN, or hold a role in an org the target belongs to (the visibility ListOrgMembers already grants at Viewer). Non-admins get the same PermissionDenied for not-found as for not-allowed, so ids and emails cannot be probed. Denials are audit-logged. Verified over gRPC against Postgres 16: stranger by id, by email and for a nonexistent id denied; self, shared-org Viewer and SYSTEM_ADMIN allowed.

2. FSD-004, the post-fold registry surface. No document in either repo specified what each registry route and RPC becomes once the slice composes into CIRISServer. FSD-004 maps every one to its post-fold path, substrate call, caller tier and node posture, and lists the decisions needed before any new route ships. It is revised for CC 1.0-rc5's authority triad (CC 2.4.1.2.1: licensure, grant, delegation are three objects, and only delegation chains):

  • a licensing-authority caller tier (the authority_id key or its license-scoped delegate; an org names itself by its org UUID per CC 3.3.9 / licensure authority_id must be the org_id, not PartnerRecord.organization_id (CC 3.3.9 ruling, rc5) #139);
  • RegisterPartner split into recognition (partner_record, steward quorum) and licence issuance (licensure:{authority_id} status rows, eight statuses, suspended reversible and revoked terminal, a set not a scalar);
  • GET /v1/licensure/{key_id}?authority= returning the per-authority fold, with a stranger's row admitted as testimony only;
  • §4.5: there is no community licence to issue (standing is the CC 3.2 T1 self-root; the bond stays off-wire), and CIRISPortal becomes CIRISClient CSDs, with the missing licensure/grant CSDs named;
  • custodial signing (GenerateKeyPair / RequestSignature) settled as DROP under CC 4.1.1;
  • two new decisions naming what rc5 still carries that contradicts this (partner_role community values, partner_record.license_type).

3. ciris-registry-core builds two ways. standalone (default, unchanged deployment) keeps sqlx, tonic, persist's postgres backend, the gRPC services and the full HTTP router. Built without default features it carries none of them, which is what CIRISServer can depend on; CI now fails if sqlx, tonic or tokio-postgres returns to that graph. fold::router(engine, node_key_id) serves the KEEP routes that already ran on persist alone (/v1/steward-key, /v1/trust-root/bundle, /v1/agent_files/{kind}); the standalone router mounts the same handlers, so the two cannot drift. tests/fold_router.rs mounts it on a SQLite Engine and passes in both builds. No wire change in the standalone build.

Found, not changed: POST /v1/integrity/auth reports authorized: true for any Bearer prefix without validating the token (FSD-004 §7). Its consumers need checking before the fix.

Issue

Relates to #62 (fold epic), #133, #139, #41. Fixes the GetUser authorization gap (no issue filed).

AI-assistance disclosure

  • No substantial AI assistance, OR
  • AI-assisted — disclosed here, human-reviewed: drafted with Claude Code (FSD text, feature split, fold router, authz fix, gRPC verification against Postgres 16). Human author to review and stand behind before merge.

Checklist

  • A human author understands and stands behind this change
  • If touching FSD/CEG/**: not touched; FSD-004 adds no wire fields
  • Tests / build pass: cargo test --locked --workspace, cargo test -p ciris-registry-core --no-default-features, clippy clean in both builds

🤖 Generated with Claude Code

https://claude.ai/code/session_017Hhka84Xw4xHjSfBq1fuw8


Generated by Claude Code

… / verify 16.1 (#76)

Pins the substrate to exactly the tags CIRISServer 0.5.217 and its
in-tree ciris-lens-core carry, so absorbing registry-core adds no second
persist/edge/verify rev to the composed build. No source changes needed
across the gap. Each git pin gains a `version = "N"` major guard (the
lens-core convention); bin rust-version aligned to the workspace 1.86.

Also fixes /v1/identity serving 4-of-6 keys: since CIRISEdge#333 the
Reticulum transport refuses `signer: None`, so edge_runtime's identity
init had been failing (WARN) since v3.0.0. It now gets the registry's
own federation identity via HybridCrypto::build_edge_local_signer.

Verified: workspace tests + db_integration green; v3.0.0 -> this build
boot upgrade on Postgres 16 applies persist V136-V152 and serves 6/6
identity keys. Rollback floor recorded in CHANGELOG: v3.0.0 will not
boot against a DB that has V136+ applied (dual-write on).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Hhka84Xw4xHjSfBq1fuw8
Both RPCs did no authorization: any valid JWT could read any user and
all of their org memberships. Now: the user themself, SYSTEM_ADMIN, or
a caller with any role in an org the target belongs to (the visibility
ListOrgMembers already grants at Viewer). Non-admins get the same
PermissionDenied for not-found as for not-allowed, so ids and emails
cannot be probed. Denials are audit-logged.

Verified over gRPC against Postgres 16: stranger (by id, by email,
nonexistent id) denied; self, shared-org Viewer, SYSTEM_ADMIN allowed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Hhka84Xw4xHjSfBq1fuw8
…an depend on

FSD-004 (draft) maps every registry route and RPC to its post-fold path,
substrate call, caller tier (public / signed / org-member / owner /
loopback / blessed infra:* key / accord quorum) and node posture
(client / proxy / unblessed / blessed server), with nine decisions
listed before any new route or capability name ships.

ciris-registry-core gains a `standalone` default feature holding sqlx,
tonic, persist's postgres backend, the gRPC services and the full HTTP
router. Built without default features it carries none of them, and CI
now fails if any returns to that graph. fold::router(engine, key_id)
serves the KEEP routes that already ran on persist alone
(/v1/steward-key, /v1/trust-root/bundle, /v1/agent_files/{kind}); the
standalone router mounts the same handlers. tests/fold_router.rs mounts
it on a SQLite Engine and passes in both builds.

No wire change in the standalone build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Hhka84Xw4xHjSfBq1fuw8
…gation)

CC 2.4.1.2.1 (rc5, #100 with the CIRISPersist#814 ruling) makes
licensure, grant and delegation three objects with three wire shapes,
and only delegation chains. The registry's LicenseType / partner_record
path folded all three into one "license".

- Rule 4 states the triad and its consequence for the surface.
- New caller tier A, licensing authority: the authority_id key or a
  license-scoped delegate resolving to it; an org names itself by its
  org UUID (#139). Q stays walled off from A and M (CC 4.2.1).
- RegisterPartner splits into recognition (partner_record, steward
  quorum) and licence issuance (licensure:{authority_id} status rows,
  eight statuses, suspended reversible / revoked terminal, a set not a
  scalar). RevokeEntity(license) becomes a status row by the authority.
- New GET /v1/licensure/{key_id}?authority= returning the per-authority
  fold; negative licensure answers are per authority.
- §4.5 maps every licence-ish field to its leg and states that there is
  no community licence to issue: standing is the CC 3.2 T1 self-root,
  the bond stays off-wire. Portal becomes CIRISClient CSDs, with the
  missing licensure / grant CSDs named.
- Rule-4 obligations on the fold router: emitter-resolves-to-authority
  fold, status sets, directional exact scope matching, no walks over
  licences or grants.
- Decisions: custodial signing settled as DROP (CC 4.1.1); Portal
  settled in direction; two new decisions on what rc5 still carries
  that contradicts this (partner_role community values,
  partner_record.license_type) and the Verify transition.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Hhka84Xw4xHjSfBq1fuw8
…d_until vs lapsed, tier heading

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Hhka84Xw4xHjSfBq1fuw8
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Carry the 2026-09-22 ruling (CIRISServer#537): the ciris-canonical
community's founders are accord-conferred, human-rooted steward keys;
installs join as members and hold standing, never a vote; the admission
quorum is the accord's. FSD-004 rule 1 no longer quotes "a vote, never
a verdict" (that sentence was never in MISSION §1.5; it came from the
server's REGISTRY_FOLD_DERISK.md, which the ruling also re-cuts). A node
gains neither.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Hhka84Xw4xHjSfBq1fuw8
Rewrite §2.1 on the 2026-09-22 ruling (CIRISServer#537). The per-install
steward keypairs casting a 2-of-3 are withdrawn. Two kinds of key, never
fused: steward keys (human, accord-conferred, sign licences and
partner_record, confer infra:attest) and install keys (owner-bound,
admitted as members of ciris-canonical, serve and replicate, no vote and
no verdict). The rotation arc is re-cut with every step human-signed.
§1.2 item 4 and §2.1.1 follow; two §6 open questions are answered
(consumers pin the GenesisBundle; installs are admitted, not bootstrapped).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Hhka84Xw4xHjSfBq1fuw8
@emooreatx
emooreatx merged commit 88a7b81 into main Sep 28, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants