@prefig/upact 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,46 @@ All notable changes to this project are documented here. Format follows [Keep a
4
4
 
5
5
  ---
6
6
 
7
+ ## [Unreleased]
8
+
9
+ ### Removed
10
+
11
+ - `CONTRIBUTING.md`, `GOVERNANCE.md`, `ROADMAP.md`. Load-bearing content folded into `README.md` (maintenance posture, commit conventions including the `Co-Authored-By:` exclusion) and `SPEC.md` (working-group definition self-contained in §11; authorship note self-contained at the top). Decision lineage lives in `git log` and the `SPEC.md` §12 register. The Decision 12 deployment-shape table moved to `docs/adapter-shapes.md`. The repo's earlier institutional shape exceeded what a single-maintainer experiment in v0.x earns.
12
+
13
+ ### Changed
14
+
15
+ - `SPEC.md`: stripped audit-framing references in §4, §5.1, §11, §12, §13 prose, leaving the substantive content intact. The footer no longer points at deleted documents.
16
+ - `package.json` `files` array: drops the deleted documents from the published package.
17
+
18
+ ---
19
+
20
+ ## [0.1.2] — 2026-05-04
21
+
22
+ Documentation-only release in this package. The substantive change is a new external adapter (`@prefig/upact-mastodon`) shipped separately.
23
+
24
+ ### Added
25
+
26
+ - ROADMAP Decision 12 (closed): multi-instance fediverse exception to Path B. The default adapter strategy (Path B / OIDC + Authentik) is incompatible with substrates whose UX requires per-login instance flexibility; `@prefig/upact-mastodon` is the first direct adapter shipped under this exception.
27
+ - `SPEC.md §13` non-normative entry for `@prefig/upact-mastodon`.
28
+ - `docs/adapter-shapes.md`: Mastodon column added to the comparison table; Mastodon-specifics section documents the per-login instance discovery + dynamic OAuth client registration pattern; F2 (per-user-session binding) is now empirically observed in two shipped adapters rather than predicted.
29
+ - `docs/cross-adapter-findings.md`: H1 confirmation note for F1/F2/F3/F6/G1 (originally Mastodon-as-analysis findings; now empirically supported by the shipped adapter).
30
+ - `README.md`: `@prefig/upact-mastodon` row added to the Adapters table.
31
+
32
+ ### Changed
33
+
34
+ - ROADMAP line 13 (adapter strategy): nuanced from blanket "Path B for all OIDC-shaped substrates" to "Path B for stable per-deployment instance configuration; direct adapters for per-login instance flexibility per Decision 12."
35
+
36
+ ### Unchanged (explicit non-changes)
37
+
38
+ - No changes to `SPEC.md §1` through `§12` (normative spec text).
39
+ - No changes to `src/types.ts`, `src/runtime.ts`, `src/errors.ts`, `src/index.ts`, or any test under `tests/`.
40
+ - No changes to the capability vocabulary (still `'email' | 'recovery'`).
41
+ - No changes to MUST clauses in §7.
42
+
43
+ The runtime kernel is unchanged from v0.1.1.
44
+
45
+ ---
46
+
7
47
  ## [0.1.1] — 2026-05-01
8
48
 
9
49
  Additive spec amendments: lifecycle and provenance on `Upactor`. Ships `@prefig/upact-oidc` as the third reference adapter.
package/CONFORMANCE.md CHANGED
@@ -27,6 +27,8 @@ Copy this template into your adapter package as `CONFORMANCE.md` and fill in eac
27
27
 
28
28
  For each capability listed, name the concrete consumer (the application code that gates on it).
29
29
 
30
+ Note: authorization roles (admin, moderator, operator) MUST NOT appear here. They are out of scope for the port (§3.1).
31
+
30
32
  ## AuthError mapping table
31
33
 
32
34
  | Substrate error | AuthErrorCode |
@@ -66,7 +68,7 @@ This adapter passes a sixteen-vector reflection test (`tests/back-channel.test.t
66
68
 
67
69
  ## Substrate
68
70
 
69
- Supabase Auth a managed PostgreSQL-backed identity service. `auth.users` is the substrate user record. Enforcement-camp substrate: the Supabase `User` object exposes email, phone, JWT claims, `app_metadata`, and `user_metadata`; the adapter strips all of these except what is needed to populate the three `Upactor` fields.
71
+ Supabase Auth: a managed PostgreSQL-backed identity service. `auth.users` is the substrate user record. Enforcement-camp substrate: the Supabase `User` object exposes email, phone, JWT claims, `app_metadata`, and `user_metadata`; the adapter strips all of these except what is needed to populate the three `Upactor` fields.
70
72
 
71
73
  ## Threat model
72
74
 
@@ -74,8 +76,8 @@ Casual coordination. Supabase Auth is a centralised service operated by Supabase
74
76
 
75
77
  ## Capabilities self-declared
76
78
 
77
- `['email', 'recovery']` for users with a confirmed email address (non-empty `user.email`).
78
- `[]` for users without a confirmed email address.
79
+ `['email', 'recovery']`: for users with a confirmed email address (non-empty `user.email`).
80
+ `[]`: for users without a confirmed email address.
79
81
 
80
82
  Concrete consumer: dyad M1 UI gates on `capabilities.has('email')` to show or hide email-related settings. `recovery` is bundled with `email` because the same email address that identifies the user is the recovery channel; they are not independently affords.
81
83
 
@@ -107,7 +109,7 @@ None.
107
109
 
108
110
  ## Identifier derivation
109
111
 
110
- `Upactor.id` is set directly from `user.id` the Supabase Auth UUID. Supabase UUIDs are opaque random identifiers (`gen_random_uuid()`); they are not derivable from user-supplied identifiers (email, phone). No hashing is applied. The raw UUID is not email-shaped and carries no information about the user visible at the application layer.
112
+ `Upactor.id` is set directly from `user.id` (the Supabase Auth UUID). Supabase UUIDs are opaque random identifiers (`gen_random_uuid()`); they are not derivable from user-supplied identifiers (email, phone). No hashing is applied. The raw UUID is not email-shaped and carries no information about the user visible at the application layer.
111
113
 
112
114
  ---
113
115
 
@@ -121,7 +123,7 @@ None.
121
123
 
122
124
  ## Substrate
123
125
 
124
- SimpleX Chat daemon a local IPC process exposing a JSON command API. Pre-conforming substrate: the SimpleX local profile carries `localDisplayName`, `agentUserId` (UUID), and a handful of status flags. There is no central directory; profiles are application-scoped and anonymous. The adapter is thin mostly type translation.
126
+ SimpleX Chat daemon: a local IPC process exposing a JSON command API. Pre-conforming substrate: the SimpleX local profile carries `localDisplayName`, `agentUserId` (UUID), and a handful of status flags. There is no central directory; profiles are application-scoped and anonymous. The adapter is thin: mostly type translation.
125
127
 
126
128
  ## Threat model
127
129
 
@@ -129,9 +131,9 @@ Anonymous / pseudonymous coordination. The SimpleX substrate has no central dire
129
131
 
130
132
  ## Capabilities self-declared
131
133
 
132
- `[]` no capabilities declared for v0.1.
134
+ `[]`: no capabilities declared for v0.1.
133
135
 
134
- The SimpleX substrate affords messaging (`sendMessage`, `receiveMessage`) and peer-to-peer matching. Neither is surfaced through the upact port at v0.1: no shipped consumer gates on a `messaging` or `p2p_matching` capability check. Per the minimum-viable discipline (CONTRIBUTING.md), capabilities land when a concrete consumer surfaces them.
136
+ The SimpleX substrate affords messaging (`sendMessage`, `receiveMessage`) and peer-to-peer matching. Neither is surfaced through the upact port at v0.1: no shipped consumer gates on a `messaging` or `p2p_matching` capability check. Capabilities land when a shipped consumer needs them, per `SPEC.md` §5.2.
135
137
 
136
138
  ## AuthError mapping table
137
139
 
@@ -159,4 +161,4 @@ None.
159
161
 
160
162
  ## Identifier derivation
161
163
 
162
- `Upactor.id` is derived from `user.agentUserId` (a UUID string) via `SHA-256(agentUserId).slice(0, 32)` the first 32 hex characters of the SHA-256 digest. The derivation is deterministic per `agentUserId` (stable across sessions for the same profile) and not reversible from the application layer. The hash is implemented with the Web Crypto API (`crypto.subtle.digest`).
164
+ `Upactor.id` is derived from `user.agentUserId` (a UUID string) via `SHA-256(agentUserId).slice(0, 32)`: the first 32 hex characters of the SHA-256 digest. The derivation is deterministic per `agentUserId` (stable across sessions for the same profile) and not reversible from the application layer. The hash is implemented with the Web Crypto API (`crypto.subtle.digest`).
package/README.md CHANGED
@@ -6,22 +6,34 @@ A typed architectural contract between a social application and any identity pro
6
6
 
7
7
  upact is named for the Ulysses pact that adopters make when building privacy-first social technologies that are architecturally hostile to extractive practices.
8
8
 
9
- upact is a minimum-disclosure anti-corruption layer between your application and any supported identity-management substrate. The privacy minima at the port (no email, no phone, no IP, opaque sessions, enumerated capabilities) are commitments the application can no longer violate. The architectural cost of breaking the contract later is what makes the commitment durable: an application built on upact cannot quietly pivot to surveillance-driven, data-retention-driven, or third-party-sharing-shaped revenue without tearing up its foundations.
9
+ upact is a minimum-disclosure anti-corruption layer between your application and any supported identity-management substrate. The privacy minima at the port (no email, no phone, no IP, opaque sessions, enumerated capabilities) are commitments the application can no longer violate *through the upact-shaped path*. An application built on upact cannot quietly pivot the identity layer toward surveillance, retention, or third-party data-sharing without visible architectural change.
10
10
 
11
- The constraint also shapes design. When the application cannot know a user's email, you build features that don't need it. This provides friction against reflexes inherited from an extraction- and retention-shaped social media ecosystem. That friction matters especially with LLM-assisted development, where those patterns are efficiently automated by tools trained on that ecosystem.
11
+ The port does not block pivots reachable through direct substrate-library import. Those remain visible to code review only. See §7.5 below and `SECURITY.md` for the limit.
12
+
13
+ The constraint also shapes design. When the application cannot know a user's email, you build features that don't need it. This provides friction against reflexes inherited from an extraction- and retention-shaped social media ecosystem.
12
14
 
13
15
  upact is not a replacement for OIDC clients (`auth.js`, `lucia`, `openid-client`), identity-broker IDPs (Authentik, Keycloak, ZITADEL), or identity protocols (DIDs, Verifiable Credentials). It is the typed-contract layer that sits on top of these substrates and provides a contract on what the application is permitted to know and what it has bound itself out of knowing.
14
16
 
15
17
  ## Why upact
16
18
 
17
- The values commitment is what motivates platforms to consider adopting upact. These benefits accrue to every adopter who honours the contract, regardless of whether the values posture is what brought them in:
19
+ These benefits accrue to every application using upact, regardless of motivation:
18
20
 
19
21
  - **Design discipline.** Structural limits on what you can know force design principles and features that don't depend on data you shouldn't have.
20
22
  - **A small, well-defined identity surface.** The `Upactor` type has three fields. Substrate-shaped User types typically have >=30.
21
- - **Reduced surface for accidental privacy violations.** If application code can't read `email`, it can't accidentally include it in logs, metrics, error reports, SSR-rendered HTML, or analytics events. The architectural constraint doubles as a debugging-and-monitoring privacy guarantee.
23
+ - **Privacy guarantees at call sites that use the port.** Code that uses `currentUpactor` cannot accidentally include email in logs, metrics, error reports, SSR-rendered HTML, or analytics events. Code that calls the substrate directly remains a code-review concern; treating substrate-library imports as a marked, audited boundary (e.g. only allowed in the substrate seam, forbidden in service code) extends the guarantee to the rest of the application.
22
24
  - **Audited opacity primitives.** Sessions cannot be unwrapped via `JSON.stringify`, `structuredClone`, `util.inspect`, or other inspection vectors. The runtime kernel is centrally tested across sixteen vectors; every conforming adapter inherits the guarantee.
23
- - **Swap substrates without re-architecting.** The port carries your privacy posture. Switch from Supabase Auth to an OIDC-brokered provider and your application code is unchanged.
24
- - **A pre-written audit artefact.** When a third party (legal, compliance, regulator, partner) asks "what does your application know about users?", the conformance statement is the answer.
25
+ - **Swap identity providers without re-architecting the identity layer.** The port carries your privacy posture across providers. Switch from Supabase Auth to an OIDC-brokered provider, and the application's identity-shaped call sites are unchanged. Substrate concerns outside identity (data access, RLS, jobs, admin tooling) remain substrate-coupled by design; upact does not abstract data access.
26
+ - **A ready answer for compliance, legal, and regulator questions about user data.** When a third party asks "what does your application know about users?", the conformance statement documents what the adapter returns. It narrows but does not replace an application-level audit, since application code remains free to import substrate libraries directly per §7.5.
27
+
28
+ ## Limits: what upact does not prevent
29
+
30
+ upact closes the identity boundary architecturally. Application-level misuse of the substrate is not closed:
31
+
32
+ - An application that ignores the port and calls the substrate library directly (e.g. `supabase.auth.getUser()`) gets back whatever the substrate exposes, including email and other fields the port would have stripped. upact's binding holds only for code that consumes `Upactor` values from the port.
33
+ - The application's freedom to import substrate libraries is preserved (§7.5). That coupling is transparent (visible in `package.json` and reviewable in code), but the port does not block it.
34
+ - Authorization (admin, moderator, operator) is out of scope (§3.1). upact does not provide a permissions model.
35
+
36
+ The discipline that makes the binding stick: treat substrate-library imports as a marked, audited boundary, only allowed in the substrate seam (the adapter, the auth callback handler), forbidden in service code. A future v0.2 conformance test suite will mechanise this; today it is convention. See `SECURITY.md` for the full scope.
25
37
 
26
38
  ## Usage
27
39
 
@@ -29,7 +41,7 @@ The values commitment is what motivates platforms to consider adopting upact. Th
29
41
  import type { IdentityPort } from '@prefig/upact';
30
42
  import { SubstrateUnavailableError } from '@prefig/upact';
31
43
 
32
- // authenticate returns Session | AuthError discriminate on 'code':
44
+ // authenticate returns Session | AuthError; discriminate on 'code':
33
45
  const result = await port.authenticate({ kind: 'password', email, password });
34
46
  if ('code' in result) {
35
47
  switch (result.code) {
@@ -51,7 +63,7 @@ try {
51
63
  }
52
64
  ```
53
65
 
54
- `authenticate` communicates auth failures as return values (`AuthError`) it never throws for wrong passwords or rate limits. `currentUpactor` and `issueRenewal` throw `SubstrateUnavailableError` for substrate outages they never return error values. The asymmetry is deliberate: auth failures are expected control-flow; substrate outages are exceptional conditions.
66
+ `authenticate` communicates auth failures as return values (`AuthError`); it never throws for wrong passwords or rate limits. `currentUpactor` and `issueRenewal` throw `SubstrateUnavailableError` for substrate outages; they never return error values. The asymmetry is deliberate: auth failures are expected control-flow; substrate outages are exceptional conditions.
55
67
 
56
68
  ## Adapters
57
69
 
@@ -60,38 +72,46 @@ try {
60
72
  | `@prefig/upact-supabase` | Supabase Auth | Enforcement | v0.1.0 shipped |
61
73
  | `@prefig/upact-simplex` | SimpleX Chat daemon | Pre-conforming | v0.1.0 shipped |
62
74
  | `@prefig/upact-oidc` | Any OIDC-compliant IDP (Dex, Authentik, Keycloak, ZITADEL) | Enforcement | v0.1.0 shipped |
75
+ | `@prefig/upact-mastodon` | Mastodon REST API (any user-chosen instance) | Enforcement | v0.1.0 shipped |
76
+ | `@prefig/upact-ember` | ember (temporary scope membership renewed in presence) | Pre-conforming | v0.1.0 shipped; first `renewable: 'represence'` adapter |
77
+ | `@prefig/upact-eudi` | EUDI wallet (OpenID4VP 1.0 / HAIP verifier, SD-JWT VC German PID) | Enforcement | v0.1.0 shipped; declared attribute list (CIR (EU) 2025/848) enforced at construction |
63
78
 
64
79
  ## Adopters
65
80
 
66
81
  | Application | Substrate | Notes |
67
82
  |---|---|---|
68
- | [dyad.berlin](https://dyad.berlin) | `@prefig/upact-supabase` | First adopter; M1 integration in progress |
83
+ | [dyad.berlin](https://dyad.berlin) | `@prefig/upact-supabase` | Reference adopter; database layer substrate-agnostic, application routes return `Upactor` |
69
84
 
70
85
  If your application uses upact, open a PR to add it here.
71
86
 
72
87
  ## In this repo
73
88
 
74
- - `SPEC.md` normative specification, v0.1.
75
- - `src/types.ts` reference TypeScript types (`Upactor`, `IdentityPort`, `AuthError`, `Capability`, `Session`).
76
- - `src/runtime.ts` small runtime kernel. Exports `createSession`, the canonical factory that produces opaque `Session` values per SPEC.md §7.4. Adapter authors should use it rather than maintain their own opaque-wrapper class; the opacity guarantee is centralised here, audited once.
77
- - `docs/adapter-shapes.md` type-only sketches of the v0.1 shipped adapters (Supabase, SimpleX, OIDC).
78
- - `docs/cross-adapter-findings.md` cross-substrate observations that shaped the spec.
79
- - `examples/sveltekit-supabase/` minimal SvelteKit + Supabase integration showing the three key wiring points: hook, type augmentation, and capability-gated page load.
80
- - `CONTRIBUTING.md` the five-test contributor audit, AI-Involvement trailer convention.
81
- - `GOVERNANCE.md` v0.x maintainer posture and v1.0 working-group target.
82
- - `CONFORMANCE.md` — conformance statement template with filled-in examples for both v0.1 adapters.
83
- - `CHANGELOG.md` — per-version change record.
84
- - `ROADMAP.md` — open and closed decisions.
89
+ - `SPEC.md`: normative specification, v0.1.
90
+ - `src/types.ts`: reference TypeScript types (`Upactor`, `IdentityPort`, `AuthError`, `Capability`, `Session`).
91
+ - `src/runtime.ts`: small runtime kernel. Exports `createSession`, the canonical factory that produces opaque `Session` values per SPEC.md §7.4. Adapter authors should use it rather than maintain their own opaque-wrapper class; the opacity guarantee is centralised here, audited once.
92
+ - `docs/adapter-shapes.md`: type-only sketches of the v0.1 shipped adapters (Supabase, SimpleX, OIDC, Mastodon).
93
+ - `docs/cross-adapter-findings.md`: cross-substrate observations that shaped the spec.
94
+ - `examples/sveltekit-supabase/`: minimal SvelteKit + Supabase integration showing the three key wiring points: hook, type augmentation, and capability-gated page load.
95
+ - `CONFORMANCE.md`: conformance statement template with filled-in examples for both v0.1 adapters.
96
+ - `CHANGELOG.md`: per-version change record.
85
97
 
86
98
  ## Status
87
99
 
88
- v0.1.1. Three reference adapters shipped. Breaking changes between v0.x revisions are permitted; v1.0 marks the first stable version.
100
+ v0.1.2. Four reference adapters shipped (OIDC added in 0.1.1, Mastodon added in 0.1.2). Breaking changes between v0.x revisions are permitted; v1.0 marks the first stable version.
101
+
102
+ upact is maintained by a group of maintainers. Issues welcome at [github.com/prefig/upact/issues](https://github.com/prefig/upact/issues). At v1.0 the core capability vocabulary (§5.1) and MUST clauses (§7) move to a working group of ≥3 conforming-adapter authors (see `SPEC.md` §11).
103
+
104
+ ## Commit conventions
105
+
106
+ Commits with substantive AI involvement carry an `AI-Involvement: <tier>` trailer recording the character of involvement (`autonomous` / `authored` / `collaborative` / `assisted` / `commit-message-only`), so readers can calibrate the provenance of normative content. The convention is most important on `SPEC.md`, `src/types.ts`, and capability-vocabulary changes.
107
+
108
+ upact does not use `Co-Authored-By:` for AI authorship. `Co-Authored-By:` claims human co-authorship; `AI-Involvement` records the character of involvement instead.
89
109
 
90
110
  ## Licence
91
111
 
92
112
  Dual-licensed:
93
113
 
94
- - **`SPEC.md`, `docs/`, this README, and other prose** CC BY 4.0 (see `LICENSE`).
95
- - **`src/` (TypeScript types and runtime kernel)** Apache-2.0 (see `LICENSE-CODE`).
114
+ - **`SPEC.md`, `docs/`, this README, and other prose**: CC BY 4.0 (see `LICENSE`).
115
+ - **`src/` (TypeScript types and runtime kernel)**: Apache-2.0 (see `LICENSE-CODE`).
96
116
 
97
117
  Each `src/*.ts` file carries an `SPDX-License-Identifier: Apache-2.0` header. The split follows TC39 / SPDX precedent: spec text is intellectual property meant to be cited and forked under CC; runtime code is software under a conventional permissive licence with a patent grant.
package/SECURITY.md CHANGED
@@ -30,4 +30,4 @@ The following are **out of scope**:
30
30
 
31
31
  ## Conformance and security
32
32
 
33
- A conforming adapter's security posture is described in its `CONFORMANCE.md`. Applications that need stronger threat-model guarantees (e.g. adversarial-context coordination) SHOULD choose a substrate whose threat model matches see `SPEC.md §10`.
33
+ A conforming adapter's security posture is described in its `CONFORMANCE.md`. Applications that need stronger threat-model guarantees (e.g. adversarial-context coordination) SHOULD choose a substrate whose threat model matches; see `SPEC.md §10`.
package/SPEC.md CHANGED
@@ -4,7 +4,7 @@
4
4
  **Status:** Working draft. Public release. Breaking changes between v0.x revisions are permitted; v1.0 marks the first stable version.
5
5
  **License:** CC BY 4.0
6
6
 
7
- > **A note on authorship.** v0.1 of this specification was AI-co-authored under the project's `AI-Involvement` trailer convention (see `CONTRIBUTING.md`). The transparency posture is preserved as disclosure rather than authorship-purity. Every commit touching normative content carries the trailer; readers can audit the contribution lineage in `git log`. Future revisions may include a maintainer-only re-authoring pass; for now the disclosure trail is the binding mechanism.
7
+ > **A note on authorship.** v0.1 of this specification was AI-co-authored. Commits touching normative content carry an `AI-Involvement: <tier>` trailer recording the character of involvement (`autonomous` / `authored` / `collaborative` / `assisted` / `commit-message-only`). The contribution lineage is auditable in `git log`. Future revisions may include a maintainer-only re-authoring pass.
8
8
 
9
9
  ---
10
10
 
@@ -47,6 +47,20 @@ An **application** conforms to this specification when it:
47
47
 
48
48
  Conformance is to a specific version of the spec.
49
49
 
50
+ **Application-side conformance is currently a discipline, not a test.** The provider has a sixteen-vector reflection test for §7.5 conformance; the application side has no equivalent automated check. A future v0.2 release SHOULD ship application-side static checks (lint rules forbidding substrate-library imports outside the substrate seam, or equivalent) to mechanise this bar.
51
+
52
+ ### §3.1 Scope: user-tier identity
53
+
54
+ This specification governs user-facing identity — the relationship between an authenticated person and the application. It does not govern:
55
+
56
+ - Application-internal authorization (admin, moderator, operator capabilities)
57
+ - System-to-system credentials
58
+ - Any identity concern that is not a direct person–application relationship
59
+
60
+ **Authorization is out of scope.** The privacy minima of §7 describe what the application may know about a user; they do not describe what an operator may know about, or do to, the system. How an application implements operator/administrative tooling is its own concern — whether through a substrate-native capability claim, an application-layer role, a separate authentication plane on a different surface, a command-line tool, or no operator surface at all.
61
+
62
+ `Upactor` attributes and capabilities MUST NOT be used to model authorization roles. The capability vocabulary in §5 governs what user-facing features the application may use (e.g. sending an email to a user); it does not govern what privileged operations a particular caller is permitted to perform.
63
+
50
64
  ## §4. Upactor
51
65
 
52
66
  A `Upactor` is a value of the following shape:
@@ -66,7 +80,7 @@ interface IdentityLifecycle {
66
80
  }
67
81
  ```
68
82
 
69
- Five fields, three of which are optional. The shape is intentionally minimal per the contributor audit (CONTRIBUTING.md). `lifecycle` and `provenance` were deferred from the initial draft and brought back by the OIDC adapter (Phase C, v0.1.1) concrete consumer need surfaces them. See §12 for the remaining deferred-decisions register.
83
+ Five fields, three of which are optional. `lifecycle` and `provenance` were deferred from the initial draft and brought back by the OIDC adapter (Phase C, v0.1.1) when it concretely needed them. See §12 for the remaining deferred-decisions register.
70
84
 
71
85
  ### §4.1 `id`
72
86
 
@@ -109,7 +123,7 @@ Capabilities are self-described provider features. The application uses capabili
109
123
  | `email` | The provider can deliver email to this identity. |
110
124
  | `recovery` | The provider supports identity recovery flows. |
111
125
 
112
- **The vocabulary is intentionally minimal.** The audit (see CONTRIBUTING.md) found these two are the capabilities that have shipped consumers the Supabase reference adapter declares them; dyad's UI gating consumes them for password-reset and invitation flows. Capabilities present in earlier drafts (`messaging`, `p2p_matching`, `presence_renewal`, `threshold_attestation`, `push`, `webauthn`) were not declared by any shipped consumer in this version and were deferred. Adapter authors do not pre-emptively expand the vocabulary; new capabilities land via §5.2 extension when concrete consumers surface.
126
+ **The vocabulary is intentionally minimal.** These two capabilities have shipped consumers: the Supabase reference adapter declares them; dyad's UI gates on them for password-reset and invitation flows. New capabilities land via §5.2 extension when shipped adapters need them.
113
127
 
114
128
  This minimum-viable discipline is itself part of the binding mechanism. A capability vocabulary that grew speculatively would dilute the contract: applications would gate on capabilities that no substrate genuinely supports, providers would declare capabilities to look feature-rich, and the whole signal would erode. Keeping the vocabulary small and concrete-need-driven keeps the binding genuine.
115
129
 
@@ -185,6 +199,8 @@ interface AuthError {
185
199
 
186
200
  Providers MUST return one of these codes; substrate-specific detail goes in `message`. Applications branch on `code` for substrate-portable error handling. Adapter packages document their per-substrate mapping (which substrate errors map to which code) in their conformance statement (§10).
187
201
 
202
+ Adapters may emit a subset of the vocabulary. Codes that distinguish identity-existence from credential-validity (notably `identity_unavailable`) are reserved for substrates that surface that distinction; the v0.1 reference adapters do not — Supabase conflates "no such user" with "wrong password" as credential-stuffing resistance, OIDC surfaces failures as token errors. Application code can branch on the full vocabulary; some branches will be unreachable for some adapters.
203
+
188
204
  ## §7. Privacy minima (normative MUST NOT clauses)
189
205
 
190
206
  These clauses are normative. A conforming provider MUST observe them.
@@ -273,11 +289,11 @@ This specification is versioned. v0.1 is the first public draft. Breaking change
273
289
 
274
290
  Capabilities present in §5.1 are normative for v0.1. Capabilities outside §5.1 are advisory and follow registry conventions to be specified in v0.2. The registry MAY accept new capabilities on demonstrated implementation by at least two independent providers AND demonstrated consumption by at least one application.
275
291
 
276
- Governance posture: v0.x decisions are made by the maintainer. By v1.0, decisions about the core capability vocabulary (§5.1) and MUST clauses (§7) move to a working group of ≥3 conforming-adapter authors. See `GOVERNANCE.md`.
292
+ Governance posture: v0.x decisions are made by the upact maintainer group on rough consensus. At v1.0, decisions about the core capability vocabulary (§5.1) and MUST clauses (§7) move to a working group of ≥3 conforming-adapter authors. A *conforming-adapter author* is an organisation or individual who maintains a published `@prefig/upact-*` package that passes the sixteen-vector reflection test, has shipped a `CONFORMANCE.md` against a specific spec version, and has actively maintained the adapter (a PR or release in the last twelve months). Capability-vocabulary additions and MUST-clause changes after v1.0 require a working-group decision plus implementation by ≥2 independent conforming adapters and consumption by ≥1 shipped application; MUST-clause changes additionally require a migration period during which the old behaviour remains valid.
277
293
 
278
294
  ## §12. Deferred decisions (the register)
279
295
 
280
- The audit (CONTRIBUTING.md) trimmed v0.1 to the minimum-viable surface that shipped consumers concretely need. Items below are deferred not abandoned, just held until a concrete consumer surfaces or a shipped adapter forces the question.
296
+ v0.1 was scoped to what shipped adapters need. Items below were proposed but did not ship in v0.1 because no adapter required them yet. They reactivate when a shipped adapter forces the question.
281
297
 
282
298
  | Decision | Substance | Why deferred from v0.1 |
283
299
  |---|---|---|
@@ -291,7 +307,7 @@ The audit (CONTRIBUTING.md) trimmed v0.1 to the minimum-viable surface that ship
291
307
  | ~~**G1 — OIDC scope discipline**~~ | **Closed v0.1.1.** `@prefig/upact-oidc` ships `validateScopes` runtime guard and documents the scope allow-list in its conformance statement. | Phase C adapter ships with runtime enforcement. |
292
308
  | **Convene + Reticulum substrate sketches in `docs/adapter-shapes.md`** | Speculative entries removed | No shipped adapter, no concrete consumer. Sketches return alongside their adapter. |
293
309
 
294
- The audit checklist in CONTRIBUTING.md names the five tests every Decision passes (concrete-need / minimum-viable / substrate-agnosticism / binding-integrity / disclosure). Future contributors apply the same checklist; this register records the exit conditions for each deferred Decision.
310
+ A Decision exits §12 when a shipped adapter has a concrete need for it (substrate-agnosticism: the change makes sense across at least one enforcement-camp and one pre-conforming substrate), the change preserves the §7 MUST NOTs (binding-integrity), and the spec amendments propagate to the §9 conformance template, the §10 security considerations, and this register where applicable (disclosure).
295
311
 
296
312
  ## §13. Non-normative appendix — provider sketches
297
313
 
@@ -301,14 +317,18 @@ Brief sketches of providers shipped or deferred against this port.
301
317
 
302
318
  - **`@prefig/upact-supabase`** — Supabase Auth substrate. Capabilities: `email`, `recovery` for users with email; `recovery` only for those without. Identity stable for the account lifetime; renewable via password reset. The port hides email, password, magic-links, JWT claims, `app_metadata`, `user_metadata` from the application; `capabilities.has('email')` gates email-bound features.
303
319
 
304
- - **`@prefig/upact-simplex`** — SimpleX Chat substrate (anonymous unidirectional queues, no central directory). Capabilities: `[]` (no `email`, no `recovery`; substrate affordances for messaging and p2p-matching are real but documented in the adapter README rather than declared as capabilities see §5 / CONTRIBUTING.md audit). Identity stable per loaded profile; renewable by re-loading the profile. No email, no recovery, no central user database.
320
+ - **`@prefig/upact-simplex`** — SimpleX Chat substrate (anonymous unidirectional queues, no central directory). Capabilities: `[]` (no `email`, no `recovery`; substrate affordances for messaging and p2p-matching are real but documented in the adapter README rather than declared as capabilities, since no shipped consumer gates on them). Identity stable per loaded profile; renewable by re-loading the profile. No email, no recovery, no central user database.
305
321
 
306
322
  **Shipped at v0.1.1:**
307
323
 
308
324
  - **`@prefig/upact-oidc`** — generic OIDC client adapter delegating substrate-specific machinery to a substrate-side IDP (Authentik, Keycloak, ZITADEL, Dex). One adapter, many configured IDPs; substrate-specific machinery in IDP realm config rather than in adapter code. Adding a substrate (Mastodon-via-broker, GitHub-via-broker, etc.) becomes operational rather than code-level. Ships `lifecycle` (JWT `exp` → `expires_at`, `renewable: 'reauth'`), `provenance` (`substrate: 'oidc'`, `instance: issuer`), and runtime scope enforcement (`validateScopes`). 75 unit tests + 11 Dex integration tests.
309
325
 
326
+ **Shipped at v0.1.2:**
327
+
328
+ - **`@prefig/upact-mastodon`** — direct Mastodon REST API adapter (per-login instance discovery, dynamic OAuth client registration, no token expiry). Substrate is "any Mastodon-API-compatible server"; Mastodon proper validated, forks (Pleroma, Akkoma, GoToSocial) MAY work via API compatibility but are not validated at v0.1.0. Capabilities: `[]`. Lifecycle: `expires_at: undefined`, `renewable: 'reauth'` (Mastodon access tokens never auto-expire per F6). Provenance: `{ substrate: 'mastodon', instance: <origin> }`. Identifier derivation: `sha256(actor.url)[:32]` per F3. Exists alongside `@prefig/upact-oidc` rather than replacing it: deployments with a fixed instance keep Path B (OIDC + Authentik); deployments wanting fediverse-flexibility (any user-chosen instance) use this package. See `docs/adapter-shapes.md` for the deployment-shape table.
329
+
310
330
  The application code does not change across these. The deployment chooses the provider; the port carries the rest.
311
331
 
312
332
  ---
313
333
 
314
- *Document version: 0.1.1. Audit-trimmed and AI-co-authored under disclosure 2026-05-01. See `ROADMAP.md` for the full decision lineage and `CONTRIBUTING.md` for the audit discipline.*
334
+ *Document version: 0.1.2. AI-co-authored under disclosure (see authorship note above). Decision lineage is in `git log` and the §12 register.*
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  * subpath — see SPEC.md §7.4 and `docs/adapter-shapes.md`.
9
9
  */
10
10
  export type { Capability, IdentityLifecycle, Upactor, UserIdentity, // deprecated alias for v0.1.x compat; removed in v0.2
11
- Session, AuthError, AuthErrorCode, IdentityPort, } from './types.js';
11
+ PresentationRequest, Presentation, Session, AuthError, AuthErrorCode, IdentityPort, } from './types.js';
12
12
  export { createSession } from './runtime.js';
13
13
  export { SubstrateUnavailableError } from './errors.js';
14
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA;;;;;;;;GAQG;AAEH,YAAY,EACX,UAAU,EACV,iBAAiB,EACjB,OAAO,EACP,YAAY,EAAE,sDAAsD;AACpE,OAAO,EACP,SAAS,EACT,aAAa,EACb,YAAY,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA;;;;;;;;GAQG;AAEH,YAAY,EACX,UAAU,EACV,iBAAiB,EACjB,OAAO,EACP,YAAY,EAAE,sDAAsD;AACpE,mBAAmB,EACnB,YAAY,EACZ,OAAO,EACP,SAAS,EACT,aAAa,EACb,YAAY,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC;;;;;;;;GAQG;AAaH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC;;;;;;;;GAQG;AAeH,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC"}
package/dist/types.d.ts CHANGED
@@ -72,6 +72,41 @@ export interface Upactor {
72
72
  * @deprecated Use `Upactor` instead.
73
73
  */
74
74
  export type UserIdentity = Upactor;
75
+ /**
76
+ * A presentation request a relying party issues to a keyring/wallet, asking the
77
+ * holder to present membership. Substrate-agnostic, and named to map onto an
78
+ * OpenID4VP authorization request so a Digital Credentials API adapter is a
79
+ * mechanical shim rather than a redesign:
80
+ * nonce -> `nonce` (replay binding)
81
+ * audience -> `client_id` (who the presentation is for)
82
+ * scopes -> `dcql_query` / `presentation_definition` constraint
83
+ * The wire encoding is the substrate's; this is the shape an app reasons about.
84
+ */
85
+ export interface PresentationRequest {
86
+ /** Single-use challenge the verifier issued; the presentation must echo it. */
87
+ nonce: Uint8Array | string;
88
+ /** The verifier this presentation is for (its origin). Binds against relay. */
89
+ audience: string;
90
+ /** Optional restriction to specific scopes; omitted means "whatever you hold". */
91
+ scopes?: string[];
92
+ }
93
+ /**
94
+ * A verifiable presentation supplied as authentication (or renewal) evidence,
95
+ * the response to a {@link PresentationRequest}. The envelope is
96
+ * substrate-agnostic; `vpToken` is the substrate's opaque presentation token,
97
+ * named to map onto an OpenID4VP `vp_token` response so the DC-API path stays a
98
+ * shim. An adapter verifies `vpToken` against the scope root(s) it trusts and
99
+ * returns an {@link Upactor} carrying a `represence` lifecycle. See the
100
+ * @prefig/upact-ember README for the concrete field mapping.
101
+ */
102
+ export interface Presentation {
103
+ /** Echoes the request nonce. */
104
+ nonce: Uint8Array | string;
105
+ /** The audience the holder addressed the presentation to, if any. */
106
+ audience?: string;
107
+ /** Substrate-specific verifiable presentation token (maps to `vp_token`). */
108
+ vpToken: Uint8Array;
109
+ }
75
110
  /**
76
111
  * A provider-shaped credential exchange result. Opaque to the application.
77
112
  * The only valid use is to pass it back to the port (e.g. for invalidate).
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,UAAU,CAAC;AAE9C;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IACjC,0EAA0E;IAC1E,UAAU,CAAC,EAAE,IAAI,CAAC;IAClB;;;;;OAKG;IACH,SAAS,EAAE,QAAQ,GAAG,YAAY,GAAG,OAAO,CAAC;CAC7C;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,OAAO;IACvB,kFAAkF;IAClF,EAAE,EAAE,MAAM,CAAC;IACX,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC;IACtC;;;;OAIG;IACH,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B;;;;;OAKG;IACH,UAAU,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACtD;AAED;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC;AAEnC;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,MAAM,CAAC;CAChC;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GACtB,oBAAoB,GACpB,qBAAqB,GACrB,uBAAuB,GACvB,sBAAsB,GACtB,cAAc,GACd,aAAa,CAAC;AAEjB,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,aAAa,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC5B,YAAY,CAAC,UAAU,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IAChE,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IAC1D,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,YAAY,CACX,QAAQ,EAAE,OAAO,EACjB,QAAQ,EAAE,OAAO,GACf,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CAC3B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,UAAU,CAAC;AAE9C;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IACjC,0EAA0E;IAC1E,UAAU,CAAC,EAAE,IAAI,CAAC;IAClB;;;;;OAKG;IACH,SAAS,EAAE,QAAQ,GAAG,YAAY,GAAG,OAAO,CAAC;CAC7C;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,OAAO;IACvB,kFAAkF;IAClF,EAAE,EAAE,MAAM,CAAC;IACX,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC;IACtC;;;;OAIG;IACH,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B;;;;;OAKG;IACH,UAAU,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACtD;AAED;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC;AAEnC;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IACnC,+EAA+E;IAC/E,KAAK,EAAE,UAAU,GAAG,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC5B,gCAAgC;IAChC,KAAK,EAAE,UAAU,GAAG,MAAM,CAAC;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,OAAO,EAAE,UAAU,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,MAAM,CAAC;CAChC;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GACtB,oBAAoB,GACpB,qBAAqB,GACrB,uBAAuB,GACvB,sBAAsB,GACtB,cAAc,GACd,aAAa,CAAC;AAEjB,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,aAAa,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC5B,YAAY,CAAC,UAAU,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IAChE,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IAC1D,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,YAAY,CACX,QAAQ,EAAE,OAAO,EACjB,QAAQ,EAAE,OAAO,GACf,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CAC3B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prefig/upact",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "upact — identity port: a typed architectural contract between a social application and any identity provider, with privacy minima as the translation rule.",
5
5
  "license": "(CC-BY-4.0 AND Apache-2.0)",
6
6
  "type": "module",
@@ -21,10 +21,7 @@
21
21
  "LICENSE",
22
22
  "LICENSE-CODE",
23
23
  "SPEC.md",
24
- "ROADMAP.md",
25
24
  "CHANGELOG.md",
26
- "CONTRIBUTING.md",
27
- "GOVERNANCE.md",
28
25
  "CONFORMANCE.md",
29
26
  "SECURITY.md"
30
27
  ],
package/CONTRIBUTING.md DELETED
@@ -1,87 +0,0 @@
1
- # Contributing to upact
2
-
3
- ## How the spec grows
4
-
5
- upact operates on **inverse kinematics**: the spec grows from concrete consumer need, not from speculative design. A change to `SPEC.md`, `src/types.ts`, or the capability vocabulary begins with an adapter author or application developer who needs something the current spec does not provide — not the other way around.
6
-
7
- Before proposing a spec change, run the five-test audit below. All five tests apply; a change that fails any one does not land.
8
-
9
- ### The five-test audit
10
-
11
- **1. Concrete-need.** Does a shipped or actively-in-development adapter *require* this change to function correctly? "Would be useful for" is not sufficient. Name the adapter and the specific operation that fails without the change.
12
-
13
- **2. Minimum-viable.** Is this the smallest change to the spec (types, MUST clauses, capability vocabulary) that satisfies the concrete need? If a narrower change would work, make that one instead.
14
-
15
- **3. Substrate-agnosticism.** Does the change make sense for at least two structurally different substrates — one enforcement-camp, one pre-conforming (see `docs/adapter-shapes.md`)? A change that only works for one substrate shape is a substrate-specific extension, not a spec change.
16
-
17
- **4. Binding-integrity.** Does the change preserve the self-binding contract? Spec changes MUST NOT:
18
- - Expose additional substrate fields through the port (email, phone, IP, substrate-internal handles)
19
- - Add OPTIONAL fields to `Upactor` whose presence reveals substrate-type information to the application
20
- - Weaken the Session opacity guarantee (§7.4)
21
- - Weaken the adapter back-channel closure requirement (§7.5)
22
-
23
- **5. Disclosure.** Does the change require documentation of a new disclosure obligation for conforming adapters — in the conformance statement (§9), the security considerations (§10), or the deferred-decisions register (§12)?
24
-
25
- ### Worked example: adding `lifecycle`
26
-
27
- When `lifecycle: { expires_at?: number; renewable: ... }` was first proposed for `Upactor`, the audit looked like this:
28
-
29
- - **Concrete-need:** OIDC adapter needs `jwt.exp` for cookie renewal decisions. Named consumer: dyad M2 SvelteKit layout, `+layout.server.ts` session check. — *passes.*
30
- - **Minimum-viable:** Could the adapter just re-call `issueRenewal` instead? Yes, for dyad M2. The field is pre-emptive. — *fails.*
31
- - **Result:** Deferred to §12 (D-SPEC-4). Returns when the OIDC adapter concretely needs it.
32
-
33
- ### Worked example: SimpleX `messaging` capability
34
-
35
- When `messaging` was proposed as a capability for `@prefig/upact-simplex`:
36
-
37
- - **Concrete-need:** SimpleX affords messaging. No current dyad UI gates on `capabilities.has('messaging')`. No shipped consumer. — *fails.*
38
- - **Result:** `@prefig/upact-simplex` ships with `capabilities = []`. Capability lands when a consumer surfaces.
39
-
40
- ## Adapter conformance
41
-
42
- New adapters MUST pass the five-test audit for any spec amendment their design requires. Adapters that work within the existing spec need not run the audit — the spec is already there.
43
-
44
- All adapters MUST:
45
-
46
- 1. Ship a `CONFORMANCE.md` (template in this repo) declaring the spec version, substrate, threat model, capability self-declaration, and `AuthError` mapping table.
47
- 2. Ship a sixteen-vector reflection test (see `@prefig/upact-supabase/tests/back-channel.test.ts`) asserting that no sentinel substrate token leaks through the adapter instance.
48
- 3. Use `createSession` from `@prefig/upact` for Session construction, or pass an equivalent sixteen-vector test suite.
49
-
50
- ## AI-Involvement trailers
51
-
52
- upact acknowledges LLM contributions to normative content under an `AI-Involvement` trailer on git commits. The trailer records the character of AI involvement so readers can calibrate the provenance of what they are reading.
53
-
54
- ```
55
- AI-Involvement: <tier>
56
- ```
57
-
58
- Five tiers:
59
-
60
- | Tier | When to use |
61
- |---|---|
62
- | `autonomous` | AI generated the full commit with no human review of content (rare; avoid for normative paths) |
63
- | `authored` | AI wrote the primary content; human reviewed and accepted with minor edits |
64
- | `collaborative` | Human and AI contributed substantial portions; hard to separate authorship |
65
- | `assisted` | Human wrote the primary content; AI provided suggestions or structured material the human evaluated |
66
- | `commit-message-only` | AI wrote only the commit message; content is human-authored |
67
-
68
- The trailer is advisory, not mechanical. Its purpose is honest attribution, not compliance theatre. Use the tier that most accurately describes the commit.
69
-
70
- Normative paths (`SPEC.md`, `src/types.ts`, capability vocabulary) that carry `autonomous` or `authored` tiers are permitted at v0.1. This posture is revisited before v1.0.
71
-
72
- ## Spec versioning and stability
73
-
74
- - v0.x: breaking changes between minor versions are permitted. The maintainer (Theodore Evans) makes decisions.
75
- - v1.0: first stable version. Decisions about the core capability vocabulary (§5.1) and MUST clauses (§7) move to a working group of ≥3 conforming-adapter authors. See `GOVERNANCE.md`.
76
-
77
- ## What goes in the deferred-decisions register
78
-
79
- `SPEC.md §12` is the register of features that passed the concrete-need test *eventually* — meaning a concrete need exists in principle — but failed minimum-viable or substrate-agnosticism, or whose consumer is not yet shipped. Items enter the register with a brief rationale; they reactivate when the consumer surfaces or a shipped adapter forces the question.
80
-
81
- Do not treat §12 entries as a backlog. They are a deliberate holding pattern, not a roadmap.
82
-
83
- ## Submissions
84
-
85
- - Open an issue before a PR for anything that touches `SPEC.md` or `src/types.ts`.
86
- - Adapter packages are independent repos under the `@prefig/` namespace; open issues on the relevant adapter repo.
87
- - Discussion happens in issues. Normative decisions land in commits. The commit message carries the decision; the issue carries the context.
package/GOVERNANCE.md DELETED
@@ -1,34 +0,0 @@
1
- # Governance
2
-
3
- ## v0.x (current)
4
-
5
- Theodore Evans is the maintainer. Decisions about the spec, capability vocabulary, and normative content are made by the maintainer, informed by adapter authors and application developers through issues and PRs.
6
-
7
- The inverse-kinematics principle (see `CONTRIBUTING.md`) is the primary constraint on spec growth. The maintainer applies it consistently; so should anyone making a case for a change.
8
-
9
- Decisions are recorded in commits to `SPEC.md` and `ROADMAP.md`, not in issue threads. Issue threads carry context; the commit is the decision.
10
-
11
- ## v1.0 (target)
12
-
13
- Decisions about the core capability vocabulary (§5.1) and MUST clauses (§7) move to a working group of ≥3 conforming-adapter authors at v1.0. A *conforming-adapter author* is an organisation or individual who:
14
-
15
- - Maintains a published `@prefig/upact-*` package that passes the sixteen-vector reflection test,
16
- - has shipped a `CONFORMANCE.md` against a specific version of the spec, and
17
- - is actively maintaining the adapter (a PR or release in the last twelve months).
18
-
19
- The working group operates on rough consensus. When rough consensus is absent, the maintainer retains casting vote.
20
-
21
- Additions to the capability vocabulary after v1.0 require a working group decision AND demonstration of:
22
-
23
- 1. Implementation by ≥2 independent conforming adapters, and
24
- 2. Consumption by ≥1 shipped application.
25
-
26
- Changes to MUST clauses require the same bar plus a migration period during which the old behaviour remains valid.
27
-
28
- ## Forks
29
-
30
- upact is dual-licensed (CC BY 4.0 / Apache-2.0; see `LICENSE` and `LICENSE-CODE`). Forks are permitted; they are asked to rename the package to avoid registry confusion. Forked packages that claim upact conformance MUST reference the specific version of the spec they conform to in their `CONFORMANCE.md`.
31
-
32
- ## Conflicts
33
-
34
- Disputes about spec interpretation go to the issue tracker. The maintainer resolves them by issuing a normative clarification (a commit to `SPEC.md`). Disputes about governance itself go to the working group at v1.0; before v1.0, they go to the maintainer.
package/ROADMAP.md DELETED
@@ -1,107 +0,0 @@
1
- # upact roadmap
2
-
3
- Last updated: 2026-05-01 (rev. 7 — v0.1.1 shipped: lifecycle + provenance, Phase C OIDC adapter, Dex integration tests).
4
-
5
- Open work for the v0.2 release window. Closed items are retained for institutional record.
6
-
7
- ## Posture (load-bearing for everything below)
8
-
9
- upact is a self-binding contract for values-aligned platform builders. The privacy minima at the port — no email, no phone, no IP, opaque sessions, enumerated capabilities — are commitments the application has structurally given up the ability to violate. The architectural cost of later breaking the contract is what makes the commitment durable.
10
-
11
- upact is not a replacement for OIDC clients (`auth.js`, `lucia`, `openid-client`), identity-broker IDPs (Authentik, Keycloak, ZITADEL), or identity protocols (DIDs, Verifiable Credentials). It is the typed-contract layer above them.
12
-
13
- **Adapter strategy (Path B).** Enforcement-camp substrates with OIDC-shaped auth (Supabase, Mastodon, Auth0, etc.) consume upact via `@prefig/upact-oidc`, which delegates substrate-specific machinery to a substrate-side IDP (Authentik, ZITADEL, etc.). Pre-conforming substrates (SimpleX, Reticulum) use direct adapters. This split is reflected in `docs/adapter-shapes.md`.
14
-
15
- ## Open
16
-
17
- ### Decision 3 — `issueRenewal` semantics normative
18
-
19
- **Posture.** Supabase adapter renews the cookie holder (substrate-holder semantics); SimpleX adapter compares the freshly-read agentUserId against the prior `Upactor.id` and refuses renewal on mismatch (identity-bound semantics). Option A (identity-bound) is recommended per planning conversation — it is the more honest posture for a substrate where id stability is part of the contract.
20
-
21
- **Status.** Deferred in `SPEC.md §12` (D3) until divergence between adapters becomes a concrete consumer issue. The §6.4 wording remains advisory.
22
-
23
- ### Decision 6 — `provenance` field on `Upactor`
24
-
25
- `provenance: { substrate: string; instance?: string }` for cross-substrate disambiguation. Shipped with the OIDC adapter (Phase C); concrete consumer: cross-IDP discrimination in multi-IDP deployments. Open question: whether Supabase and SimpleX adapters should also populate provenance for parity.
26
-
27
- ### Decision 7 — `continuation` field on `Upactor`
28
-
29
- `continuation: { prior_id: string; kind: 'rotation' | 'migration' | 'rekey' | 'reauth' }` for substrate-known identifier transitions. No shipped substrate currently emits transitions through the port. Reactivated when AP `Move`, SimpleX rotation, or similar arrives with a shipped adapter.
30
-
31
- ### Decision 8 — `watch` on `IdentityPort`
32
-
33
- `IdentityPort.watch(context): AsyncIterable<Upactor | null>`. Push-shaped substrates need an adapter first; the streaming-primitive choice (AsyncIterable vs Observable vs callback) deferred until a concrete push substrate ships.
34
-
35
- ### v0.2 conformance test suite
36
-
37
- **Posture.** A published conformance test suite (referenced in `SPEC.md §9` as "TBD") that adapter authors run to claim conformance mechanically. Currently only the sixteen-vector reflection test is standardised; the rest of the conformance bar is prose.
38
-
39
- **Status.** Targeted for v0.2. Funding (OSA) would accelerate this.
40
-
41
- ## Closed
42
-
43
- ### v0.1.1 (2026-05-01)
44
-
45
- **Shipped.** OIDC adapter (Phase C), lifecycle + provenance spec amendments, Dex integration test suite.
46
-
47
- ---
48
-
49
- ### Phase C: `@prefig/upact-oidc` adapter
50
-
51
- **Closed 2026-05-01.** Third reference adapter shipped: enforcement-camp, OIDC-shaped. `createOidcAdapter(config, cookies)` factory — closure-captured cookie jar, no enumerable substrate state. PKCE (S256) + authorization-code flow, signed-cookie state and session management, transparent refresh on expiry, HMAC-SHA256 signed cookies. Scope policy enforces `email`/`phone`/`address`/`groups` exclusion at construction time. 75 unit tests + 11 Dex integration tests. Permanent Dex dev rig at `upact-oidc-poc/`.
52
-
53
- ### Decision 9 (close) — `issueRenewal` normatively OPTIONAL
54
-
55
- **Closed 2026-05-01.** See above; moved from Open.
56
-
57
- ### `lifecycle` + `provenance` on `Upactor` (v0.1.1 spec amendment)
58
-
59
- **Closed 2026-05-01.** Optional `lifecycle?: IdentityLifecycle` and `provenance?: { substrate: string; instance?: string }` added to `Upactor`. `IdentityLifecycle = { expires_at?: Date; renewable: 'reauth' | 'represence' | 'never' }`. Purely additive — existing adapters continue to work without populating these fields. The OIDC adapter populates both. Normative text in `SPEC.md §4.4` and `§4.5`.
60
-
61
- ---
62
-
63
- ### v0.1.0 (2026-05-01)
64
-
65
- **Shipped.** First public draft of spec and runtime. All items below were completed as part of the v0.1.0 release window.
66
-
67
- ---
68
-
69
- ### `Upactor` rename across the codebase
70
-
71
- **Closed 2026-05-01.** `UserIdentity` → `Upactor` throughout: `SPEC.md`, `src/types.ts`, `src/index.ts`, both adapter packages (`src/adapter.ts`, `src/identity-mapper.ts`, tests). Deprecated alias `UserIdentity = Upactor` retained for v0.1.x compatibility; removed at v0.2. Method `currentIdentity` → `currentUpactor` on the port.
72
-
73
- ### Decision 4 — `AuthErrorCode` vocabulary normative in `§6.5`
74
-
75
- **Closed 2026-05-01.** Six-member normative union: `credential_invalid`, `credential_rejected`, `substrate_unavailable`, `identity_unavailable`, `rate_limited`, `auth_failed`. Both reference adapters declare their mapping table in `CONFORMANCE.md`. Applications branch on `code` for substrate-portable error handling.
76
-
77
- ### Decision 11 — Adapter package back-channel closure (conformance bar)
78
-
79
- **Closed 2026-05-01.** Normative text added to `SPEC.md §7.5`: conforming adapters MUST hold substrate state in closure scope (factory pattern) or `#private` fields, not on enumerable instance properties. `(adapter as any).client` MUST return `undefined`. Both reference adapters (upact-supabase, upact-simplex) retrofitted to factory-only; `SupabaseUpactAdapter` and `SimpleXUpactAdapter` class forms removed. Both ship sixteen-vector back-channel reflection tests at `tests/back-channel.test.ts`.
80
-
81
- ### `AI-Involvement` trailer convention
82
-
83
- **Closed 2026-05-01.** Five-tier vocabulary documented in `CONTRIBUTING.md` and `ROADMAP.md`. Adopted across the prefig org for subsequent commits. Existing commits pre-convention carry no trailer; the convention start point is the v0.1.0 release.
84
-
85
- ### v0.1 SPEC authorship policy (relaxed)
86
-
87
- **Closed 2026-05-01.** The earlier commitment to a hand-rewritten normative spec before public push was relaxed for v0.1. `SPEC.md`, `src/types.ts`, and the runtime kernel ship with `AI-Involvement: authored` or `collaborative` trailer disclosure. The transparency posture (disclosure replaces authorship-purity as the binding mechanism) is consistent with the project's own values.
88
-
89
- ### SPEC §6.2 amendment — `currentUpactor` throw permission
90
-
91
- **Closed 2026-05-01.** Adapters MAY throw `SubstrateUnavailableError` (from `@prefig/upact`) when the substrate is unreachable, distinct from returning `null` when the user is not authenticated. Normative text in `SPEC.md §6.2`.
92
-
93
- ### Decision 2 — `currentIdentity` throw-vs-null contract
94
-
95
- **Closed 2026-05-01 (option C).** Typed `SubstrateUnavailableError` for substrate-down, `null` for logged-out. Both distinctions needed for quality UX without substrate coupling.
96
-
97
- ### Decision 10 — Multi-step authentication flows at the port
98
-
99
- **Closed 2026-05-01.** IDP delegation (Path B) resolves multi-step auth without growing the port. The OAuth dance happens at a substrate-side IDP; the upact adapter consumes terminal OIDC tokens. The port stays one-shot.
100
-
101
- ### Decision 1 — `OpaqueSubstrateSession._unwrap()` escape hatch
102
-
103
- **Closed 2026-05-01.** Lifted into `@prefig/upact` as `createSession` + `_unwrapSession`. Both reference adapters use `createSession`. Sixteen-vector opacity suite centrally maintained in `tests/runtime.test.ts`.
104
-
105
- ### Decision 5 — Naming the central primitive
106
-
107
- **Closed 2026-05-01.** `UserIdentity` → `Upactor`. Draws on the UML Actor lineage; coined word in the upact namespace; no external collisions; brand cohesion. Lower-case bare word — `Upactor`, not `UpActor`.