Skip to content

Access

Access is inseam’s first-class permission layer over Source data. Envelopes resolve into durable Principals; declarative rules grant or deny per (Principal, Source); a live checkAccess recheck runs on every fetch. Access is the scope leg of the Connection / Source / Access split: Connection holds reach, Source holds addresses, Access holds scope.

Two surfaces, split by package:

  • @inseam/access-contractPrincipal, Identifier, Network, Host, Envelope, EnvelopeParty, SourceParticipant, AccessGrant, the rule AST (AccessConfig, Rule, Predicate, TargetSelector), the IdentifierKindDefinition and CeremonyAdapter interfaces, event payload shapes, and the AccessError hierarchy. Zero runtime deps.
  • @inseam/core (Access contribution) — openAccessRegistry, AccessRegistry, the defineAccess builder and predicate factories, the principal-resolver and access-projection materializers, and the egress filter.

The data model rides Store tables; the read and write paths ride the Events outbox. Two core-owned materializers register against the supplied EventsRegistry:

  • access.principal-resolver consumes core.envelope_indexed, canonicalizes each party’s identifier, find-or-creates the Principal, and writes source_participant rows with effectiveTrust = min(identifier.trust, party.trust).
  • access.access-projection consumes envelope, rule-change, identifier-trust, and merge events; JOINs participants × rules × host owners; writes access_grant rows. Deny-wins. Rule changes write into a shadow projection and atomically swap.
  • openAccessRegistry({ store, events }) — host entry point. Boots the registry, registers the two core materializers, sets up the egress filter.
  • defineAccess({ rules: [...] }) — rule-set builder. Validated on setRules against the trust ladder and registered identifier kinds.
  • Predicate factoriesgrant, deny, identifierEquals, identifierMatches, principalHasRole, sourceKindIn, roleIn, all, any, not, …
  • checkAccess({ principalId, sourceId }) — authoritative read path. Re-evaluates the current rule set against current trust state. Cheap, indexed; used on every fetch.
  • listAccessibleSources(...) — projection-backed listing. Stale on a race with a rule change; callers needing authoritative state recheck per row.
  • Trust ladder — closed: claimed < provider-asserted < verified. Plugins assert at most provider-asserted from envelope data; verified rows exist only via principal.identifier_verified, which only registered ceremony adapters may emit.

Exact signatures and rule grammar: @inseam/access-contract API · arch/access/spec.md.

Plugins contribute Access vocabulary, never rules. Two extension points:

  1. Identifier kinds — implement IdentifierKindDefinition (a kind name, a scopeDiscipline description, a deterministic canonicalize). Register via access.registerIdentifierKind.
  2. Ceremony adapters — implement CeremonyAdapter (a name, supportedKinds, an async run returning a signed CeremonyAttestation). Register via access.registerCeremonyAdapter. Only registered adapters may produce principal.identifier_verified.

Rules themselves are host-owned. Plugin-defined roles MUST be plugin.scope-prefixed (e.g. "gmail.thread-owner"); the framework’s reserved role values are sender, recipient, cc, bcc, mentioned, owner.

If you find yourself wanting to gate data behind a check that isn’t expressible as (Principal, Source) → grant|deny, the right move is usually to add a new identifier kind or role, not to bypass Access.

  • LLM summary — dense reference for agents.
  • Connection — Connection-owned setup is the entry point for owner / device Principals.
  • Source — what grants attach to.
  • Events — the outbox the materializers ride.
  • arch/access/design.md — why Access is a first-class layer and the trust ladder rationale.
  • arch/access/spec.md — data model, rule AST, predicate vocabulary, projection + recheck read path, egress filter, errors, acceptance criteria.