docs: audit corrections, coverage pages, screenshots, and em-dash removal
Finalizes the docs section after an adversarial audit (code review + per-page fact-check + coherence critic) and two content requests. - Accuracy: fixed 10 invented cross-link slugs, the admin-vs-board gating claims the verifiers caught (permissions matrix, /settings, invites, onboarding, connect-payments, proposal withdraw, Stripe reconciliation, member fields, door.operate), and 2 issues in the new pages (project area-at-creation, forum thread-delete role). - Coverage: added 5 how-to pages so every module is documented (recurring Stripe dues, tasks & chores, projects, comms & forum, ops knowledge base + secrets vault). 34 pages total. - Screenshots: 12 captioned app screenshots under public/docs-media/, embedded as figures. Each tutorial now has 2-3 step screenshots. - Style: removed all em-dashes/en-dashes/ellipses from the docs (natural ASCII punctuation). - Handoff (docs/HANDOFF-audit-cleanup.md) updated with the current state. typecheck 0 · lint 0 errors · build ok (all 34 /docs pages prerendered)
This commit is contained in:
@@ -14,7 +14,7 @@ Managers configure at [/door/manage](/door/manage) and [/door/buttons](/door/but
|
||||
|
||||
## Member cards
|
||||
|
||||
`member_cards` maps a physical card UID to a member. The raw UID is treated as a credential: only `door.manage` holders can read the table, and a member's own view returns a per-card list (`card_type`, `label`, `is_active`, and the last four characters of the UID, rendered `••••ABCD`) — never more than the last four of the UID.
|
||||
`member_cards` maps a physical card UID to a member. The raw UID is treated as a credential: only `door.manage` holders can read the table, and a member's own view returns a per-card list (`card_type`, `label`, `is_active`, and the last four characters of the UID, rendered `••••ABCD`), never more than the last four of the UID.
|
||||
|
||||
| Field | Type | Notes |
|
||||
| --- | --- | --- |
|
||||
@@ -44,7 +44,7 @@ Native HeatSync encodes fixed-width query strings: grant `?m<slot>&p<perm>&t<tag
|
||||
|
||||
## Card slots
|
||||
|
||||
`door_card_slots` is the platform's allocation map. Controllers key cards by an integer `slot` (HeatSync range 0–200), scoped per connection.
|
||||
`door_card_slots` is the platform's allocation map. Controllers key cards by an integer `slot` (HeatSync range 0-200), scoped per connection.
|
||||
|
||||
- `UNIQUE (connection_id, slot)` arbitrates concurrent grants racing for a slot.
|
||||
- `UNIQUE (connection_id, card_id)` makes re-granting idempotent.
|
||||
@@ -77,8 +77,8 @@ Two transports pull entry/denied events into the log and share one core. Ingeste
|
||||
| --- | --- | --- |
|
||||
| `label` / `button_group` | text | Grouped and ordered by `sort_order` |
|
||||
| `method` | `GET`, `POST`, `PUT`, `PATCH`, `DELETE` | Default `POST` |
|
||||
| `base_url` / `pinned_host` | — | Per-button SSRF pin |
|
||||
| `url_template` / `body_template` / `headers` | — | With `secret_ref` injected server-side |
|
||||
| `base_url` / `pinned_host` |, | Per-button SSRF pin |
|
||||
| `url_template` / `body_template` / `headers` |, | With `secret_ref` injected server-side |
|
||||
| `required_permission` | catalog code | Default `apicall.invoke`; a member sees only buttons whose permission they hold |
|
||||
| `confirm` | boolean | Prompt before firing (default true) |
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ A class is a per-space offering; a session is a scheduled occurrence; a signup i
|
||||
|
||||
| Field | Notes |
|
||||
| --- | --- |
|
||||
| `title` | 1–200 chars, required |
|
||||
| `title` | 1-200 chars, required |
|
||||
| `description` | optional |
|
||||
| `payment_link` | optional `http(s)` URL only; a manual link, no live payment integration |
|
||||
| `capacity` | optional default per-session capacity; `> 0`; null = unlimited |
|
||||
@@ -62,7 +62,7 @@ A certification is a per-space type (name unique case-insensitive, `description`
|
||||
- `expires_at` is computed and stored **at grant time** from the type's `validity_months` (null = never expires). Later edits to the type never change existing grants.
|
||||
- At most one active (non-revoked) grant per member per type; revoked grants stay as history and a re-grant is allowed.
|
||||
- Revoking is a soft revoke (`revoked_at`, `revoked_by`, `revoked_reason`); grants are never hard-deleted by clients.
|
||||
- Renewing a non-revoked grant resets `granted_at` and recomputes expiry. A revoked grant is terminal — issue a fresh grant.
|
||||
- Renewing a non-revoked grant resets `granted_at` and recomputes expiry. A revoked grant is terminal, issue a fresh grant.
|
||||
|
||||
| Cert status | Rule |
|
||||
| --- | --- |
|
||||
@@ -80,7 +80,7 @@ A certification is a per-space type (name unique case-insensitive, `description`
|
||||
|
||||
Equipment is a per-space tool record; a reservation is one member's time window on one tool. Members browse and reserve at [/equipment](/equipment); managers maintain the registry at [/equipment/manage](/equipment/manage). See [Reserve equipment](/docs/how-to/equipment-reservations) for the task walkthrough.
|
||||
|
||||
Equipment fields: `name` (1–200), `description`, `location`, `asset_tag`, `is_active`, an optional `required_certification_id`, and an operational status.
|
||||
Equipment fields: `name` (1-200), `description`, `location`, `asset_tag`, `is_active`, an optional `required_certification_id`, and an operational status.
|
||||
|
||||
| Equipment status | Reservable? |
|
||||
| --- | --- |
|
||||
|
||||
@@ -10,8 +10,8 @@ Each form is one row in the `forms` table.
|
||||
|
||||
| Field | Type | Notes |
|
||||
| --- | --- | --- |
|
||||
| `slug` | text | Unique per space. Lowercase letters, numbers, and internal hyphens; 1–80 chars. Immutable after creation. |
|
||||
| `title` | text | 1–200 chars. |
|
||||
| `slug` | text | Unique per space. Lowercase letters, numbers, and internal hyphens; 1-80 chars. Immutable after creation. |
|
||||
| `title` | text | 1-200 chars. |
|
||||
| `description` | text | Optional, up to 2000 chars. |
|
||||
| `kind` | enum | `form` or `waiver`. |
|
||||
| `visibility` | enum | `public_anon`, `public_auth`, or `members`. |
|
||||
@@ -24,7 +24,7 @@ The slug is unique per space (`UNIQUE (space_id, slug)`), not globally, so two s
|
||||
|
||||
## Field types
|
||||
|
||||
The `schema` is an ordered array of fields. Each field has a `key` (lowercase letters, numbers, underscores; 1–60 chars; unique within the form), a `label`, an optional `help` string, and an optional `required` flag (default `false`). A form may have up to 200 fields.
|
||||
The `schema` is an ordered array of fields. Each field has a `key` (lowercase letters, numbers, underscores; 1-60 chars; unique within the form), a `label`, an optional `help` string, and an optional `required` flag (default `false`). A form may have up to 200 fields.
|
||||
|
||||
| `type` | Answer stored as | Server validation |
|
||||
| --- | --- | --- |
|
||||
@@ -37,7 +37,7 @@ The `schema` is an ordered array of fields. Each field has a `key` (lowercase le
|
||||
| `select` | string | must be one of `options` |
|
||||
| `radio` | string | must be one of `options` |
|
||||
|
||||
`select` and `radio` fields require an `options` array (each option 1–200 chars, up to 100 options). Answers are validated server-side against the stored schema on submit: unknown keys are discarded, empty required fields are rejected, and only known fields are persisted.
|
||||
`select` and `radio` fields require an `options` array (each option 1-200 chars, up to 100 options). Answers are validated server-side against the stored schema on submit: unknown keys are discarded, empty required fields are rejected, and only known fields are persisted.
|
||||
|
||||
## Member linking
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
The governance kernel is three linked entities — proposals, incidents, and policies — plus the enums and computed fields that drive async voting, code-of-conduct tracking, and versioned bylaws. This page enumerates every enum value, field, and lifecycle state defined in the schema. It backs the app screens at [/proposals](/proposals), [/incidents](/incidents), and [/policies](/policies), and the how-to guides [Run a proposal](/docs/how-to/run-a-proposal), [Handle incidents](/docs/how-to/handle-incidents), and [Publish a policy](/docs/how-to/publish-a-policy).
|
||||
The governance kernel is three linked entities (proposals, incidents, and policies) plus the enums and computed fields that drive async voting, code-of-conduct tracking, and versioned bylaws. This page enumerates every enum value, field, and lifecycle state defined in the schema. It backs the app screens at [/proposals](/proposals), [/incidents](/incidents), and [/policies](/policies), and the how-to guides [Run a proposal](/docs/how-to/run-a-proposal), [Handle incidents](/docs/how-to/handle-incidents), and [Publish a policy](/docs/how-to/publish-a-policy).
|
||||
|
||||
## Proposals
|
||||
|
||||
@@ -117,7 +117,7 @@ Each entry in an incident's update thread carries its own visibility. Admin and
|
||||
|
||||
## Policies
|
||||
|
||||
A policy is a versioned document — bylaws, code of conduct, station rules. Members read; admin and board create. Rows are immutable: a new version is a new row pointing back via `prior_version_id`, uniqueness enforced by `UNIQUE (space_id, slug, version)`.
|
||||
A policy is a versioned document, bylaws, code of conduct, station rules. Members read; admin and board create. Rows are immutable: a new version is a new row pointing back via `prior_version_id`, uniqueness enforced by `UNIQUE (space_id, slug, version)`.
|
||||
|
||||
### `policy_status`
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ These control delivery and cron authentication. The ones that govern integration
|
||||
| `CRON_SECRET` | Shared secret guarding both cron endpoints. Generate with `openssl rand -hex 32`. | Both cron routes return `503` and never run. |
|
||||
| `SENTRY_DSN` | Optional Sentry-compatible error backend; both cron dispatchers and the webhook report to it. | Capture seam no-ops. |
|
||||
|
||||
The two cron endpoints reuse the same `CRON_SECRET`. The door webhook does not use it — it authenticates on a per-connection secret from the vault.
|
||||
The two cron endpoints reuse the same `CRON_SECRET`. The door webhook does not use it, it authenticates on a per-connection secret from the vault.
|
||||
|
||||
## Notification dispatcher — `POST /api/cron/notifications`
|
||||
## Notification dispatcher, `POST /api/cron/notifications`
|
||||
|
||||
Transactional email is an outbox (the `notifications` table). The Stripe webhook and other server actions only enqueue rows; nothing sends inline. This endpoint drains the outbox and is meant to be hit once a minute by the droplet crontab.
|
||||
|
||||
@@ -46,13 +46,13 @@ Members opt out of muteable categories from their profile at [/me](/me); the tog
|
||||
| `forms` | Yes | `form_submission_received` |
|
||||
| `admin_alerts` | Yes | `form_submission_admin` |
|
||||
|
||||
The model is opt-out: an absent preference row means enabled. A type with no category mapping is treated as always-on (fail-open). When the dispatcher marks an email `skipped`, the notification still appears unread in the member's in-app inbox on [/me](/me) — the `read_at` marker is independent of email delivery.
|
||||
The model is opt-out: an absent preference row means enabled. A type with no category mapping is treated as always-on (fail-open). When the dispatcher marks an email `skipped`, the notification still appears unread in the member's in-app inbox on [/me](/me), the `read_at` marker is independent of email delivery.
|
||||
|
||||
## Door inbound — poll vs. webhook
|
||||
## Door inbound, poll vs. webhook
|
||||
|
||||
A space that runs a door controller can pull real entry/denied events into the access log through one of two transports, both configured per connection on [/door/manage](/door/manage) (requires the `door.manage` permission — see [Access control](/docs/reference/access-control) and [Connect a door](/docs/how-to/connect-a-door)). Both share one ingest core; ingested rows dedupe on `(connection_id, dedupe_key)`.
|
||||
A space that runs a door controller can pull real entry/denied events into the access log through one of two transports, both configured per connection on [/door/manage](/door/manage) (requires the `door.manage` permission, see [Access control](/docs/reference/access-control) and [Connect a door](/docs/how-to/connect-a-door)). Both share one ingest core; ingested rows dedupe on `(connection_id, dedupe_key)`.
|
||||
|
||||
### Poll — `POST /api/cron/door-ingest`
|
||||
### Poll, `POST /api/cron/door-ingest`
|
||||
|
||||
For native-HeatSync controllers only. A once-a-minute crontab call reads each enabled connection's `?z` log through the hardened outbound executor and matches each card to a member.
|
||||
|
||||
@@ -60,12 +60,12 @@ For native-HeatSync controllers only. A once-a-minute crontab call reads each en
|
||||
- Polls only connections with `is_enabled`, `inbound_enabled`, and `adapter = 'native_heatsync'`, up to 50 per run, concurrently.
|
||||
- Idempotent: overlapping or missed minutes are harmless.
|
||||
|
||||
### Webhook — `POST /api/door/inbound/[connection]`
|
||||
### Webhook, `POST /api/door/inbound/[connection]`
|
||||
|
||||
For generic (non-HeatSync) controllers or a LAN relay that pushes normalized events. This is the reliable transport: each event carries a caller-supplied stable id, so retries are idempotent.
|
||||
|
||||
- `[connection]` is the connection's UUID; a malformed id returns a generic `404`.
|
||||
- Auth: `Authorization: Bearer <inbound secret>` — the per-connection secret from the AES-256-GCM vault (`inbound_secret_ref`), distinct from the outbound door password. A missing connection, disabled inbound, or bad secret all return the same `401`. The webhook URL is shown on [/door/manage](/door/manage) once inbound is enabled.
|
||||
- Auth: `Authorization: Bearer <inbound secret>`, the per-connection secret from the AES-256-GCM vault (`inbound_secret_ref`), distinct from the outbound door password. A missing connection, disabled inbound, or bad secret all return the same `401`. The webhook URL is shown on [/door/manage](/door/manage) once inbound is enabled.
|
||||
- Rate limited to 120 requests/minute per connection (`429` over that). Bodies over 64 KB are rejected `413`.
|
||||
|
||||
Request body:
|
||||
@@ -84,7 +84,7 @@ Request body:
|
||||
}
|
||||
```
|
||||
|
||||
`events` holds 1–100 items. `result` is one of `granted`, `denied`, `unknown`. Provide `card_uid` and/or `card_number` (digits only) to resolve the event to a member. Valid requests return `{ "received": true, ... }` with per-batch insert counts; malformed JSON or a schema failure returns `400`.
|
||||
`events` holds 1-100 items. `result` is one of `granted`, `denied`, `unknown`. Provide `card_uid` and/or `card_number` (digits only) to resolve the event to a member. Valid requests return `{ "received": true, ... }` with per-batch insert counts; malformed JSON or a schema failure returns `400`.
|
||||
|
||||
## Email transport (Resend)
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
A member is one person's record in your space, stored in the `space_members` table. This page lists every member status and tier, the fields on the member record, and the settings that control who can see the directory. Manage members at [/members](/members); members edit their own record at [/me](/me).
|
||||
|
||||

|
||||
|
||||
## Statuses
|
||||
|
||||
`status` is the `member_status` enum. It defaults to `unverified`. It governs whether a member can act in the app and whether they hold their role's privileges.
|
||||
@@ -8,13 +10,13 @@ A member is one person's record in your space, stored in the `space_members` tab
|
||||
| --- | --- | --- | --- |
|
||||
| `current` | Yes | Yes | All members |
|
||||
| `late` | Yes | Yes | All members (dues lapsed) |
|
||||
| `unverified` | Yes | No — pending approval | Pending Approval tab |
|
||||
| `inactive` | No — blocked from all actions | No | Inactive tab |
|
||||
| `unverified` | Yes | No, pending approval | Pending Approval tab |
|
||||
| `inactive` | No, blocked from all actions | No | Inactive tab |
|
||||
|
||||
Notes:
|
||||
|
||||
- The active set is `current`, `unverified`, and `late`. Only these three are loaded as a member's active membership; `inactive` members are blocked from every server action.
|
||||
- `late` is a dues state, not an authorization downgrade — a late member keeps their role and privileges.
|
||||
- `late` is a dues state, not an authorization downgrade, a late member keeps their role and privileges.
|
||||
- `unverified` is the approval gate. In a `require_approval` space, a new member stays `unverified` and holds **no** privileged capability until an admin approves them, even if they redeemed a role-bearing invite. Approving sets `status` to `current` and `approved` to `true`.
|
||||
- The [/members](/members) page tabs are All, Payment Issues, Pending Approval (`unverified`), and Inactive. The All tab hides `inactive` members.
|
||||
|
||||
@@ -79,6 +81,6 @@ Who can see the member directory is set per space by `member_directory_visibilit
|
||||
| `members_visible` | Any authenticated member (default) |
|
||||
| `public_members_visible` | Any authenticated member; behaves identically to `members_visible` (there is no public, unauthenticated directory route) |
|
||||
| `board_only` | Elevated roles only (`admin`, `board`, `treasurer`) |
|
||||
| `member_count_visible` | Elevated roles only; treated identically to `board_only` — non-elevated members get the same restricted notice (no count is shown) |
|
||||
| `member_count_visible` | Elevated roles only; treated identically to `board_only`, non-elevated members get the same restricted notice (no count is shown) |
|
||||
|
||||
When a member is not permitted to see the directory, [/members](/members) shows a "restricted in this space" notice instead of the table.
|
||||
|
||||
@@ -23,63 +23,63 @@ Several modules span more than one screen. The primary route above is the main e
|
||||
|
||||
### Members
|
||||
|
||||
- `/members` — the member roster, dues statuses, and contact info.
|
||||
- `/customize` — where membership tiers, custom roles, and the permissions matrix are configured (admin only; board can open `/customize` but these panels are read-only for board).
|
||||
- `/contacts` — non-member contacts.
|
||||
- `/me` — a member's own membership record ("My membership").
|
||||
- `/recruitment` — prospective-member pipeline (admin only).
|
||||
- `/members`, the member roster, dues statuses, and contact info.
|
||||
- `/customize`, where membership tiers, custom roles, and the permissions matrix are configured (admin only; board can open `/customize` but these panels are read-only for board).
|
||||
- `/contacts`, non-member contacts.
|
||||
- `/me`, a member's own membership record ("My membership").
|
||||
- `/recruitment`, prospective-member pipeline (admin only).
|
||||
|
||||
### Tasks, projects, and ops
|
||||
|
||||
- `/tasks` — tasks and chores.
|
||||
- `/projects` — the project board.
|
||||
- `/ops` — ops and facilities docs, area leads, and the secrets vault. Individual docs open at `/ops/[id]`.
|
||||
- `/tasks`, tasks and chores.
|
||||
- `/projects`, the project board.
|
||||
- `/ops`, ops and facilities docs, area leads, and the secrets vault. Individual docs open at `/ops/[id]`.
|
||||
|
||||
### Payments and finance
|
||||
|
||||
- `/payments` — transaction import and reconciliation.
|
||||
- `/financials` — financial reporting.
|
||||
- `/import` — import and sync (admin only).
|
||||
- `/payments`, transaction import and reconciliation.
|
||||
- `/financials`, financial reporting.
|
||||
- `/import`, import and sync (admin only).
|
||||
|
||||
See [Connect payments](/docs/how-to/connect-payments) and [Import members](/docs/how-to/import-members).
|
||||
|
||||
### Governance
|
||||
|
||||
- `/proposals` — proposals, with detail at `/proposals/[id]` and creation at `/proposals/new`.
|
||||
- `/incidents` — incident reports (`/incidents/new`, `/incidents/[id]`).
|
||||
- `/policies` — the policy library (`/policies/new`, `/policies/[slug]`).
|
||||
- `/forum` — the member forum (`/forum/new`, `/forum/[id]`).
|
||||
- `/proposals`, proposals, with detail at `/proposals/[id]` and creation at `/proposals/new`.
|
||||
- `/incidents`, incident reports (`/incidents/new`, `/incidents/[id]`).
|
||||
- `/policies`, the policy library (`/policies/new`, `/policies/[slug]`).
|
||||
- `/forum`, the member forum (`/forum/new`, `/forum/[id]`).
|
||||
|
||||
See [Run a proposal](/docs/how-to/run-a-proposal).
|
||||
|
||||
### Forms and waivers
|
||||
|
||||
- `/my-forms` — forms available to you to fill out and sign.
|
||||
- `/forms` — the form and waiver builder (`/forms/new`, `/forms/[id]`), gated by the `forms.manage` permission.
|
||||
- `/my-forms`, forms available to you to fill out and sign.
|
||||
- `/forms`, the form and waiver builder (`/forms/new`, `/forms/[id]`), gated by the `forms.manage` permission.
|
||||
|
||||
See [Build a form](/docs/how-to/build-a-form).
|
||||
|
||||
### Classes, certifications, and equipment
|
||||
|
||||
- `/classes` — the class schedule; `/classes/manage` for scheduling and rosters.
|
||||
- `/certifications` — certification records, gated by `certifications.manage`.
|
||||
- `/equipment` — the tool registry and reservations; `/equipment/manage` for the registry.
|
||||
- `/attendance` — attendance records.
|
||||
- `/classes`, the class schedule; `/classes/manage` for scheduling and rosters.
|
||||
- `/certifications`, certification records, gated by `certifications.manage`.
|
||||
- `/equipment`, the tool registry and reservations; `/equipment/manage` for the registry.
|
||||
- `/attendance`, attendance records.
|
||||
|
||||
See [Your first class](/docs/tutorials/first-class) and [Equipment reservations](/docs/how-to/equipment-reservations).
|
||||
|
||||
### Access control
|
||||
|
||||
- `/doors` — the member-facing door and card view.
|
||||
- `/door/manage` — door connections, cards, and the access log (`door.manage`).
|
||||
- `/door/buttons` — API buttons for door actions.
|
||||
- `/doors`, the member-facing door and card view.
|
||||
- `/door/manage`, door connections, cards, and the access log (`door.manage`).
|
||||
- `/door/buttons`, API buttons for door actions.
|
||||
|
||||
See [Connect a door](/docs/how-to/connect-a-door).
|
||||
|
||||
### Onboarding and invites
|
||||
|
||||
- `/customize` — the onboarding flow builder and invite-link management (admin only).
|
||||
- `/settings` — space settings (admin only).
|
||||
- `/customize`, the onboarding flow builder and invite-link management (admin only).
|
||||
- `/settings`, space settings (admin only).
|
||||
|
||||
See [Configure onboarding](/docs/how-to/configure-onboarding) and [Invite links](/docs/how-to/invite-links).
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ The `payment_platform` enum tags every payment and every pay-here link with its
|
||||
| `cash` | Cash logged by a treasurer | No | No |
|
||||
| `stripe` | In-app recurring dues integration | No | Yes |
|
||||
|
||||
The url-based subset — `paypal`, `zeffy`, `venmo` — is the only set allowed for external pay-here links (`DUES_LINK_PLATFORMS`). `cash` and `stripe` cannot be configured as external dues links.
|
||||
The url-based subset (`paypal`, `zeffy`, `venmo`) is the only set allowed for external pay-here links (`DUES_LINK_PLATFORMS`). `cash` and `stripe` cannot be configured as external dues links.
|
||||
|
||||
## Link status
|
||||
|
||||
@@ -36,7 +36,7 @@ A backdated or historical payment therefore never moves a member's dues state ba
|
||||
|
||||
## Pay-here links (`dues_payment_methods`)
|
||||
|
||||
Admins configure external pay-here links so members can pay dues off-platform; a treasurer reconciles the payment manually later through the [/payments](/payments) flow (the platform tag pre-types that reconcile). Configuration is link-only — no automated payment record is created.
|
||||
Admins configure external pay-here links so members can pay dues off-platform; a treasurer reconciles the payment manually later through the [/payments](/payments) flow (the platform tag pre-types that reconcile). Configuration is link-only, no automated payment record is created.
|
||||
|
||||
| Column | Type | Notes |
|
||||
| --- | --- | --- |
|
||||
@@ -44,7 +44,7 @@ Admins configure external pay-here links so members can pay dues off-platform; a
|
||||
| `url` | text | Must be an absolute `https://` URL (DB CHECK `url ~* '^https://'`) |
|
||||
| `instructions` | text | Optional memo hint, e.g. "put your member name in the note" |
|
||||
| `is_active` | boolean | Only active links render to members; defaults to `true` |
|
||||
| `sort_order` | integer | Display order, `0`–`999`; defaults to `0` |
|
||||
| `sort_order` | integer | Display order, `0`-`999`; defaults to `0` |
|
||||
|
||||
There is at most one link per `(space, platform)`; saving is an idempotent upsert on that pair. The [/me](/me) self-view shows only active links, in `sort_order`; the [/settings](/settings) admin UI shows every configured method, active and inactive.
|
||||
|
||||
@@ -74,7 +74,7 @@ Two service-client paths write this table: the Stripe webhook, and `startDuesChe
|
||||
|
||||
### Status mapping
|
||||
|
||||
Raw Stripe `subscription.status` maps to a member status. Dues lapse tops out at `late` — it never auto-sets `inactive`; an admin decides inactivation manually.
|
||||
Raw Stripe `subscription.status` maps to a member status. Dues lapse tops out at `late`, it never auto-sets `inactive`; an admin decides inactivation manually.
|
||||
|
||||
| Stripe status | Member status |
|
||||
| --- | --- |
|
||||
@@ -83,11 +83,11 @@ Raw Stripe `subscription.status` maps to a member status. Dues lapse tops out at
|
||||
| `past_due` (grace exceeded) | `late` |
|
||||
| `canceled`, `unpaid`, `incomplete`, `incomplete_expired` | `late` |
|
||||
|
||||
The grace window is `current_period_end` plus the configured `grace_days` (default `7`). Stripe settings — `mode` (`test`/`live`), publishable key, the tier-to-price map, `grace_days`, and the secret/webhook refs — live in `integrations.config`; the secret key and webhook signing secret are stored in the [secrets vault](/docs/reference/secrets-vault), not in `config`. See [/settings](/settings) to configure Stripe.
|
||||
The grace window is `current_period_end` plus the configured `grace_days` (default `7`). Stripe settings live in `integrations.config`: `mode` (`test`/`live`), publishable key, the tier-to-price map, `grace_days`, and the secret/webhook refs. The secret key and webhook signing secret are stored in the encrypted [secrets vault](/docs/explanation/security-model), not in `config`. See [/settings](/settings) to configure Stripe.
|
||||
|
||||
## Related pages
|
||||
|
||||
- [/payments](/payments) — log, import, link, and reconcile payments
|
||||
- [/me](/me) — member dues self-view, pay-here links, and Stripe checkout
|
||||
- [/settings](/settings) — configure Stripe and pay-here links
|
||||
- [/docs/reference/members-and-roles](/docs/reference/members-and-roles) — member statuses and roles
|
||||
- [/payments](/payments), log, import, link, and reconcile payments
|
||||
- [/me](/me), member dues self-view, pay-here links, and Stripe checkout
|
||||
- [/settings](/settings), configure Stripe and pay-here links
|
||||
- [Members: statuses, tiers & fields](/docs/reference/members), member statuses, tiers, and fields
|
||||
|
||||
@@ -64,6 +64,6 @@ A space can also create custom roles (in the Roles section of [Customize](/custo
|
||||
|
||||
`user_effective_roles(uid, sid)` returns a member's built-in role, the slugs of any custom roles assigned to them, and an `area_lead:<id>` marker for each area they lead. `user_has_permission(uid, sid, perm)` then answers true when the member is privilege-eligible and either holds the `admin` role (implicit-all shortcut) or one of their effective roles has the permission granted in `space_role_permissions`.
|
||||
|
||||
This layer is strictly additive. A grant can only widen what a role can do through the surfaces that consult permissions; it never overrides the row-level security that isolates one space's data from another. The Permissions matrix is editable by `admin` and `board` — both the `setRolePermissions` server action and the `space_role_permissions` RLS policies authorize the two roles. The Customize UI, however, currently exposes the matrix as view-only to `board` and enables editing for `admin` only; a board member can still change permissions through the server action directly.
|
||||
This layer is strictly additive. A grant can only widen what a role can do through the surfaces that consult permissions; it never overrides the row-level security that isolates one space's data from another. The Permissions matrix is editable by `admin` and `board`, both the `setRolePermissions` server action and the `space_role_permissions` RLS policies authorize the two roles. The Customize UI, however, currently exposes the matrix as view-only to `board` and enables editing for `admin` only; a board member can still change permissions through the server action directly.
|
||||
|
||||
See also: [Modules reference](/docs/reference/modules) and [Customize your space](/docs/how-to/customize-space).
|
||||
|
||||
Reference in New Issue
Block a user