Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .claude/board/ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@

## ISS-PRESET-VERBS-PROVISIONAL — the preset roles' verbs are written from seat names, not from HubSpot

- **Status:** OPEN (D-IAM-1, 2026-10-21; operator to confirm or amend)
- **Status:** DECIDED 2026-10-21 (operator, EXO-50 message 006): AMENDED, not approved. The six role names stay. The verb table stays only as a tagged compatibility/simulation fixture, never as tenant runtime access. EffectiveAccess = seat/subscription ceiling ∧ tenant permission grants ∧ owner/team scope ∧ per-property outer+ordinal authorization ∧ action-specific gates. Super Admin is an explicit status, not a bypass of tenant isolation, schema/policy validation or seat gates. Admin is not blanket FULL/DELETE. View-only has no implicit read of sensitive fields. ACT (email send, enrolment, invoice, bulk), DELETE and EXPORT each need an explicit grant and are never inherited from WRITE/CREATE. A missing, unknown, stale or unread tenant grant fails closed and never falls back to the fixture. The presets are not reported as HubSpot-verified until a sandbox oracle exists (ISS-ORACLE-PORTAL). Code still follows the old table until the D-IAM-3 code PR.
- **Where:** `hubspo-iam::roles` (the verb table in the module docs), pinned by `each_seat_edits_exactly_its_own_concepts` and `only_the_admins_delete`.
- **What:** every preset reads every plugged concept. Edit (write + create) and act: sales on contact, company, deal, lead, call, meeting, task, note, email, line item, quote/invoice (list edit only, sequence act only); service on contact, company, ticket, call, meeting, task, note, email (list edit); marketing on contact, company, campaign, email (list, note, task edit). Only the two admins delete; admin cannot delete users and teams (`PROJECT_ACTOR`). No HubSpot export backs this; the second oracle (rule 12) would.

## ISS-PERMISSION-SETS-NOT-ROLES — tenant permission sets cannot be `RoleId`s

- **Status:** OPEN (D-IAM-1, 2026-10-21; design needed)
- **Status:** DECIDED 2026-10-21 (operator, EXO-50 message 006): permission sets are versioned tenant DATA with stable numeric ids, separate from the static `RoleId` presets. A permission set may replace a user's configured permissions and is not assumed to only narrow a preset (HubSpot user-management docs, 2026-09). Seat, role template and grant are three different things. Implementation: D-IAM-1 / D-IAM-3 code PR.
- **Where:** `lance_graph_contract::rbac::RoleId` is `&'static str`; `RbacPlug::roles` is a static list.
- **What:** a HubSpot permission set is created by the tenant at runtime, so it cannot be a plugged role without leaking strings. Candidate shape: a permission set is tenant data that narrows one preset (per-concept verbs ⊆ the preset's, field masks per D-IAM-3), applied after the preset's decision; it never widens. Needs the request path (D-IAM-3) to apply it.
## ISS-SPEAR-DOCUMENT-CONTRACTS — spear has a content store but no document record, reference or authorization

- **Status:** OPEN (found 2026-10-21 verifying D-DOC-1; upstream asks for the operator, not filed on spear)
- **Status:** FILED UPSTREAM 2026-10-21 by the operator as spear#26 (contract and design request only, no implementation authorized): tenant-scoped DocumentRecord/DocumentRef, version witnesses, document ACL, folders, documented consumer/delivery API. HubSPO-rs builds none of it; D-DOC-1 stays Blocked until spear's contracts land.
- **Where:** spear `main` `c69cacb`: `src/content/mod.rs` (implemented), `src/sharepoint/{intent,drive_scope}.rs` (types only), `src/sharepoint/transport/mod.rs` (stub), `.claude/plans/onedrive-delivery-v1.md` (nothing built).
- **What HubSPO-rs needs from spear, and must not build itself:**
1. A document record over the content store: tenant, content hash, name, media type, size, folder, provenance, version. The content store is a bare hash → bytes map written for message bodies.
Expand Down
2 changes: 1 addition & 1 deletion .claude/board/LATEST_STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@
(`GroupRoles::from_policy`), inherited through SID chains only, filtered
by the binding; an unread group refuses the request. 3 integration tests
over a dir-sim view (`tests/roles.rs`).
- Open: `ISS-PRESET-VERBS-PROVISIONAL`, `ISS-PERMISSION-SETS-NOT-ROLES`.
- 2026-10-21 ruling: the verb table is a fixture only. Runtime access must come from versioned tenant permission sets and fail closed (`ISS-PRESET-VERBS-PROVISIONAL`, `ISS-PERMISSION-SETS-NOT-ROLES`, both decided). The code still uses `Catalogue` until the D-IAM-3 code PR.

## Per-property read authorization (D-IAM-3, contract half)

Expand Down
6 changes: 3 additions & 3 deletions .claude/board/STATUS_BOARD.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@

| D-id | Deliverable | Size | Status | Evidence |
|---|---|---|---|---|
| D-IAM-1 | Role catalogue: Super Admin, Admin, Sales/Service/Marketing user, view-only, custom permission sets; grant source with `field_mask_of` | M | In progress | 2026-10-21 (authorized by the operator): the six presets ship. Role names are in `hubspo_port::RBAC_ROLES`; the grants are `hubspo_iam::roles::Catalogue` (one grant per plugged concept, provisional verb table); `hubspo_iam::roles::bind` binds the plug green. A request's roles come from the directory: `hubspo_iam::roles::request_actors` reads security-group grants (OGAR group-role policy via dir-sim `GroupRoles`/`ViewActors`) once per request and refuses on an unread group. 12 unit + 3 directory tests. Open: tenant permission sets (`ISS-PERMISSION-SETS-NOT-ROLES`); the preset verbs await operator confirmation (`ISS-PRESET-VERBS-PROVISIONAL`). 2026-10-20 inputs from lance-graph: (1) #1635/#1638 (D-RMAP-DIR-1, lab only) measured a dir-sim `View` walk as a full membership scan per call, 0.4–0.7 ms at 35k–263k rows; resolve an actor's roles once per request, never per record. The compiled closure (32–49 ns) is not admitted (ADMISSION open, D-RMAP-DIR-2 not authorized), so do not depend on `dir-rmap-probe`. (2) #1636 ACTION-GATE: an `ActionDef` with `required_role = None` skips RBAC in both `commit` and `commit_via`; the upstream recommendation is `commit_via` canonical and `None` only for actions declared public. Give every CRM action a role. |
| D-IAM-1 | Role catalogue: Super Admin, Admin, Sales/Service/Marketing user, view-only, custom permission sets; grant source with `field_mask_of` | M | In progress | 2026-10-21 (authorized by the operator): the six presets ship. Role names are in `hubspo_port::RBAC_ROLES`; the grants are `hubspo_iam::roles::Catalogue` (one grant per plugged concept, provisional verb table); `hubspo_iam::roles::bind` binds the plug green. A request's roles come from the directory: `hubspo_iam::roles::request_actors` reads security-group grants (OGAR group-role policy via dir-sim `GroupRoles`/`ViewActors`) once per request and refuses on an unread group. 12 unit + 3 directory tests. Open: tenant permission sets (`ISS-PERMISSION-SETS-NOT-ROLES`); the preset verbs await operator confirmation (`ISS-PRESET-VERBS-PROVISIONAL`). 2026-10-20 inputs from lance-graph: (1) #1635/#1638 (D-RMAP-DIR-1, lab only) measured a dir-sim `View` walk as a full membership scan per call, 0.4–0.7 ms at 35k–263k rows; resolve an actor's roles once per request, never per record. The compiled closure (32–49 ns) is not admitted (ADMISSION open, D-RMAP-DIR-2 not authorized), so do not depend on `dir-rmap-probe`. (2) #1636 ACTION-GATE: an `ActionDef` with `required_role = None` skips RBAC in both `commit` and `commit_via`; the upstream recommendation is `commit_via` canonical and `None` only for actions declared public. Give every CRM action a role. 2026-10-21 ruling (ISS-PRESET-VERBS-PROVISIONAL, amended): `Catalogue` is a fixture, not tenant runtime access; runtime grants come from versioned tenant permission sets (ISS-PERMISSION-SETS-NOT-ROLES) and fail closed. To change in the D-IAM-3 code PR. |
| D-IAM-2 | Ownership scopes (all / team / owned) as `ScopeSpec`; team hierarchy via `Dn128` subtree | M | Shipped | #30 + #31 (#31 fixes the nested-group P1 via lance-graph #1488 `members_transitive_within`; 8 tests). `hubspo-iam::scope` 2026-10-11: owner set from the dir-sim `View` (team = directly held team group + nested sub-teams), one Quack `IN` over the owner lane, stored as a mask; 6 tests, 2 disable runs red. P-AUTH-1 2026-10-10: scope as stored mask in the fold kernel ≤ 4.3 %; per-row owner check +43–49 %. Unblocked 2026-10-11: roles from directory groups via lance-graph-dir-sim `actors::ViewActors` + `GroupRoles::from_policy` (consumable by git) |
| D-IAM-3 | Field-level permissions (sensitive properties) as per-role `WideFieldMask`; absent from the wire | M | In progress | 2026-10-21 contract shipped (not yet wired to any output path): `hubspo_iam::fields` — outer `WideFieldMask` (lane, or position 0 for the run) ∧ inner `PropertyGrants` by stable ordinal; `Projection::compile` once per request, `project` drops hidden/unknown values, `require` refuses filters/sorts/aggregates on them, a projection compiled at another schema version refuses everything. 10 tests incl. `school` vs `date_of_birth`/`gender`/`marital_status`/`military_status`. Open: per-role default grants, wiring into REST/UI/export/events/workflow outputs. 2026-10-21 operator ruling (`ISS-PACKED-RUN-RBAC-GRAIN`, option b): storage grain may be a packed run, authorization grain is the property. Field 0 grants the container, never its contents; packed properties are authorized one by one by stable table ordinal, in a mask that is not the 256-position `WideFieldMask`. Requirements: compile authorized fields once per request; check each packed property before serialization, render, export and workflow use; refuse filters, sorts and aggregations on hidden properties; never hand a raw packed run to an untrusted caller (debug, cache, direct API included); fail closed on missing, unknown or stale inner policy, invalidated on grant or schema change; negative test `school` vs `date_of_birth`. Option (c), high-risk properties in their own lanes, allowed before first tenant data. 2026-10-20: also needs D-IAM-1 (roles) and D-ONT-6 (field positions per concept) in HubSPO-rs, which the upstream note below missed. 2026-10-20 unblocked upstream: lance-graph #1556 `RbacBinding::with_class_field_defaults` + `field_mask_for` (role mask, else class default, else None), OGAR #358 `GrantSource::default_field_mask_of` feeding it in `OgarRbac::bind`, lance-graph #1558 `walk_rails_authorized` (hidden field never emitted, hidden rail not followed; use `visible_fields()`). Read lance-graph `.claude/knowledge/access-control-model.md` first: policy data keyed by classid, no reasoning |
| D-IAM-3 | Field-level permissions (sensitive properties) as per-role `WideFieldMask`; absent from the wire | M | In progress | 2026-10-21 contract shipped (not yet wired to any output path): `hubspo_iam::fields` — outer `WideFieldMask` (lane, or position 0 for the run) ∧ inner `PropertyGrants` by stable ordinal; `Projection::compile` once per request, `project` drops hidden/unknown values, `require` refuses filters/sorts/aggregates on them, a projection compiled at another schema version refuses everything. 10 tests incl. `school` vs `date_of_birth`/`gender`/`marital_status`/`military_status`. Open: per-role default grants, wiring into REST/UI/export/events/workflow outputs. 2026-10-21 operator ruling (`ISS-PACKED-RUN-RBAC-GRAIN`, option b): storage grain may be a packed run, authorization grain is the property. Field 0 grants the container, never its contents; packed properties are authorized one by one by stable table ordinal, in a mask that is not the 256-position `WideFieldMask`. Requirements: compile authorized fields once per request; check each packed property before serialization, render, export and workflow use; refuse filters, sorts and aggregations on hidden properties; never hand a raw packed run to an untrusted caller (debug, cache, direct API included); fail closed on missing, unknown or stale inner policy, invalidated on grant or schema change; negative test `school` vs `date_of_birth`. Option (c), high-risk properties in their own lanes, allowed before first tenant data. 2026-10-20: also needs D-IAM-1 (roles) and D-ONT-6 (field positions per concept) in HubSPO-rs, which the upstream note below missed. 2026-10-20 unblocked upstream: lance-graph #1556 `RbacBinding::with_class_field_defaults` + `field_mask_for` (role mask, else class default, else None), OGAR #358 `GrantSource::default_field_mask_of` feeding it in `OgarRbac::bind`, lance-graph #1558 `walk_rails_authorized` (hidden field never emitted, hidden rail not followed; use `visible_fields()`). Read lance-graph `.claude/knowledge/access-control-model.md` first: policy data keyed by classid, no reasoning 2026-10-21 ruling: proceed with a first output path (contact lookup): actors resolved once per request; a projection stamped with schema, grant, role and seat versions; owner/team scope and per-ordinal grants before serialization, export, cache or diagnostics; ACT/DELETE/EXPORT gated individually. Required negative tests: missing policy, revocation, other concept or snapshot, view-only on sensitive fields, unauthorized ACT/DELETE, hidden-predicate inference. |
| D-IAM-4 | Directory ingest (ogar-ad / ogar-az / native), seat assignment as directory simulation plans | L | Queued | |
| D-IAM-5 | Partitioning (business units / brands) as tenant sub-scopes | M | Queued | |
| D-IAM-6 | Accumulation guard: exports / cohort reports require an extra grant | S | Queued | |
Expand All @@ -93,7 +93,7 @@
| D-MAIL-8 | Templates, snippets, personalisation tokens (askama-compiled, validated against the ClassView) | M | Queued | |
| D-MAIL-9 | Open / click tracking endpoints + bot/MPP heuristic flag | M | Queued | |
| D-MAIL-10 | Calls: telephony is out of scope for v1 beyond call *logging* engagements; spear D-TELEPHONY reuse later | S | Queued | 2026-10-21: the telephony API families (`crm-calling-extensions`, `crm-transcriptions`; 17 functions) are deferred in the surface map; call logging stays planned under D-API-1 CRUD. |
| D-DOC-1 | HubSpot Files API compatibility in `hubspo-docs` over spear: ingest, delivery and lookup flows (plan §2.5.1). No second content store, version history, Graph client, authorization or receipt model | M | Blocked | Operator ruling 2026-10-21 (`ISS-SURFACE-UNPLANNED`). Surface: `files-files`, 19 functions. Waits on spear: document records, references and authorization (`ISS-SPEAR-DOCUMENT-CONTRACTS`, not planned there yet) and D-DELIVER-2..6 (planned, not built). spear verified at `c69cacb`: content store implemented; `UploadIntent`/`DriveScope` types only; Graph transport a stub. |
| D-DOC-1 | HubSpot Files API compatibility in `hubspo-docs` over spear: ingest, delivery and lookup flows (plan §2.5.1). No second content store, version history, Graph client, authorization or receipt model | M | Blocked | Operator ruling 2026-10-21 (`ISS-SURFACE-UNPLANNED`). Surface: `files-files`, 19 functions. Waits on spear: document records, references and authorization (`ISS-SPEAR-DOCUMENT-CONTRACTS`, not planned there yet) and D-DELIVER-2..6 (planned, not built). spear verified at `c69cacb`: content store implemented; `UploadIntent`/`DriveScope` types only; Graph transport a stub. 2026-10-21: filed upstream as spear#26 (contract only). |

## Wave 7 — Automation: workflows, sequences, SLAs

Expand Down
Loading