Commit Graph

230 Commits

Author SHA1 Message Date
Ben Stull d687a65470 feat(slice4): POST .../meta single-entry edit endpoint + GET RFC meta/can_edit_meta (§22.4a PUC-1)
Direct-commit to the sidecar (D7), contributor+ gated (INV-4), schema-validated
at the write boundary, lazy-migrates a legacy entry, re-ingests. GET RFC now
returns the per-entry meta mapping and a can_edit_meta capability.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:00:40 -07:00
Ben Stull aee9b582e5 fix(slice4): make all entry write paths sidecar-aware (§22.4a carried from SLICE-1)
graduate, claim, retire/unretire, _read_meta_entry, mark_entry_reviewed,
body extract/wrap (api_branches + api_prs replay) now dual-read and write
metadata to the sidecar via write_entry_files + bot.commit_entry_files/
open_entry_pr — a migrated body-only .md no longer crashes entry.parse or
re-grows frontmatter; legacy entries lazy-migrate on first metadata write.
Existing tests updated to assert the sidecar (INV-2 clean docs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:57:54 -07:00
Ben Stull 734290f344 feat(slice4): bot.commit_entry_files + open_entry_pr multi-file primitives (§22.4a)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:46:09 -07:00
Ben Stull 49981e2d6e feat(slice4): metadata sidecar git read/write helpers — dual-read + lazy-migrate ops (§22.4a)
apply_values, EntryGitState, read_entry_from_git, write_entry_files, sidecar_path_for.
Plus the SLICE-4 implementation plan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:44:14 -07:00
Ben Stull 7ece6d348b Merge pull request 'v0.49.0 — SLICE-3: faceted left-pane filtering (§22.4a)' (#33) from worktree-metadata-slice3-facets into main 2026-06-08 00:44:54 +00:00
Ben Stull 6bb6d654fa release(slice3): v0.49.0 — faceted left-pane filtering (§22.4a SLICE-3) 2026-06-07 17:43:56 -07:00
Ben Stull 276a625997 test(slice3): Catalog faceted-pane component test (§22.4a PUC-3) 2026-06-07 17:41:04 -07:00
Ben Stull ae3afe2f48 feat(slice3): faceted catalog pane gated on collection fields (§22.4a §5.1) 2026-06-07 17:40:29 -07:00
Ben Stull ae083bfcaa feat(slice3): FacetGroups faceted-pane component (§22.4a §5.1) 2026-06-07 17:38:35 -07:00
Ben Stull bcce40d2cb feat(slice3): listRFCs accepts facet selections, returns {items,facets} (§22.4a) 2026-06-07 17:38:16 -07:00
Ben Stull 7b269e11c4 test(slice3): integration tests for faceted list endpoint (§22.4a PUC-3) 2026-06-07 17:37:46 -07:00
Ben Stull 27061c30b0 feat(slice3): collection list endpoint returns facets + honours filters (§22.4a) 2026-06-07 17:35:26 -07:00
Ben Stull 14ea3c0cce feat(slice3): pure facet field-set + filter/count helper (§22.4a) 2026-06-07 17:34:33 -07:00
Ben Stull 644bf35d89 feat(slice3): persist entry metadata to cached_rfcs.meta_json at ingest (§22.4a) 2026-06-07 17:33:47 -07:00
Ben Stull 3636fa5afd feat(slice3): migration 034 — cached_rfcs.meta_json for facet values (§22.4a) 2026-06-07 17:33:11 -07:00
Ben Stull 1bcf8aa77e Merge pull request 'v0.48.0 — SLICE-2: collection field schema + central validation (§22.4a)' (#32) from worktree-metadata-slice2-schema into main 2026-06-08 00:06:41 +00:00
Ben Stull e336e31812 v0.48.0 — SLICE-2: collection field schema + central validation (§22.4a)
Collections declare a `fields:` schema in `.collection.yaml`; entries carry
typed metadata (enum/tags/text). New central `metadata_schema` module parses
the schema leniently (INV-3) and validates entry values — advisory at read
(corpus mirror flags violations as `metadata_malformed` without hard-failing),
the enforcement point for the write boundary (edit endpoints land SLICE-4/5).

- app/metadata_schema.py: parse_fields (lenient/normalizing) + validate
- registry.parse_collection_manifest reads `fields:` into collection config
- collections.get_collection unpacks `fields`; served by the collection API
- cache._refresh_collection_corpus validates each entry advisory-only

Non-breaking, opt-in: no `fields:` → unchanged (INV-5); default `document`
collection declares none (N=1 unchanged). No DB migration — schema rides in
collections.config_json. SLICE-2 of
docs/design/2026-06-06-configurable-collection-metadata.md §7.2.

Backend suite green (601 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 17:06:04 -07:00
Ben Stull 98c276a662 Merge pull request 'v0.47.0 — SLICE-1: metadata sidecars (storage + dual-read + migration tool)' (#31) from worktree-metadata-slice1-sidecar into main 2026-06-07 15:24:55 +00:00
Ben Stull f05ee59763 v0.47.0 — SLICE-1: metadata sidecars — storage + dual-read + migration tool
§22.4a SLICE-1 of docs/design/2026-06-06-configurable-collection-metadata.md
(§7.2). Entry metadata can live in a per-entry `<slug>.meta.yaml` sidecar with
the `.md` kept as pure prose (INV-2). Additive and non-breaking — with no
sidecars present every corpus stays on the legacy frontmatter path,
byte-identical (N=1 unchanged).

- Dual-read (app/metadata.py `read_entry`) — sidecar-else-legacy-frontmatter,
  identical records (INV-6); unknown/forward-compat keys ride along through
  parse→serialize and migration (INV-7, `Entry.extra`). A degenerate sidecar
  (malformed/empty/slug-less) never drops the entry — slug backstopped from the
  filename stem, flagged not lost (INV-3).
- Migration tool (`metadata.migrate_collection`) — idempotent, one ChangeFiles
  commit per collection (new `gitea.change_files`). Tested as a function; its
  Owner-gated operator trigger is DEFERRED to SLICE-4 (write paths must become
  sidecar-aware first — see the design's SLICE-4 note + INV-8). No production
  trigger ships here, so no corpus is rewritten.
- Malformed flag — migration 033 adds `cached_rfcs.metadata_malformed`
  (additive); the corpus mirror derives it; catalog list + entry-detail APIs
  surface `metadata_malformed`.
- INV-7 at graduation — graduation now carries `Entry.extra` through the rebuild
  instead of dropping forward-compat keys.

Gate: backend 575 passed (28 new: test_metadata / _migration / _cache +
graduation extra-preservation). Frontend untouched. CHANGELOG 0.47.0 +
upgrade-steps; VERSION + frontend/package.json -> 0.47.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 08:24:27 -07:00
Ben Stull b1acc2382d Merge pull request 'v0.46.2 — SLICE-0: §22.4a contract amendment (entry metadata is collection-configured, not type-driven)' (#30) from worktree-metadata-slice0-spec into main 2026-06-07 14:33:21 +00:00
Ben Stull 5cb5f4a4a2 v0.46.2 — SLICE-0: §22.4a contract amendment (entry metadata is collection-configured, not type-driven)
Reframes binding SPEC.md §22.4a so a collection's entry metadata schema is
collection-configured — a `fields:` schema in `.collection.yaml` plus per-entry
`<slug>.meta.yaml` sidecars — rather than a frontmatter schema hard-wired to the
collection's `type`. Item 3's type-specific surfaces (release planning;
bdd scenario/coverage views) are deferred to a future design; bdd coverage is
recorded as a future `ref`-field surface rendered as hyperlinks (no cross-
collection corpus fusion). `type` still selects terminology (entry noun,
v0.45.0) + default initial_state/review posture (§22.4b-c).

SLICE-0 of docs/design/2026-06-06-configurable-collection-metadata.md (§7.2);
supersedes the per-type-surfaces draft (D11). Doc-only: per-type frontmatter
validation was never implemented, so no operator action, no schema/behavior
change. Sidecar storage + validation + UI arrive in SLICE-1+.

- SPEC.md §22.4a reframed; document/specification/bdd bullets updated; metadata
  amendment blockquote added.
- SPEC.md §2 and §22 forward-pointer blockquotes: "type-dependent frontmatter
  schema" -> "collection-configured, not type-driven (§22.4a, as amended)".
- CHANGELOG 0.46.2; VERSION + frontend/package.json -> 0.46.2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 07:32:27 -07:00
Ben Stull 36cb6187eb Merge pull request 'preview.env.example: flotilla-core overlay set (retire shim ref)' (#29) from fix/preview-env-flotilla-core into main 2026-06-07 05:52:03 +00:00
Ben Stull 677c5eb72f preview.env.example: flotilla-core overlay set (not the retired per-app shim)
The example comment referenced 'ohm-rfc-app-flotilla overlay set' — the retired
per-app shim. flotilla-core is the operator CLI for all deployments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 22:44:09 -07:00
Ben Stull 077563ea47 Merge pull request 'docs(spec): Corpus Tree — universal directory-tree left pane (Solution Design)' (#28) from docs/corpus-tree-spec into main 2026-06-07 00:55:02 +00:00
Ben Stull dc5345cef4 docs(spec): Corpus Tree — universal directory-tree left pane (Solution Design)
Solution Design for making the left pane a universal git-directory tree:
host existing documentation repos as path-addressed corpora, full
governance lifecycle per file, dual-mode (structure/flat) pane replacing
the §7 Catalog, zero-migration onboarding ("no record = active").

Discovery output of ohm session 0081.0. Conforms to handbook §3.3
Solution Design standard. Sliced SLICE-1..4 in §7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 17:47:24 -07:00
Ben Stull ee74a39b62 Merge pull request 'v0.46.1 — fix migration 029 for the §22.13 re-stamp aftermath (OHM deploy fault)' (#27) from fix/migration-029-orphan-collection into main v0.46.1 2026-06-06 19:03:20 +00:00
Ben Stull 2f507e5721 v0.46.1 — fix migration 029 for the §22.13 re-stamp aftermath (OHM deploy fault)
Deploying the three-tier series onto OHM crash-looped on migration 029:
`NOT NULL constraint failed: cached_branches__new.collection_id`, then a UNIQUE
collision. Root cause: the v0.39.0 default→ohm re-stamp updated
cached_rfcs.project_id but NOT the entry-satellite tables, so ~1.3k
cached_branches rows were stranded at project_id='default' — which 029's
per-project collection backfill can't map (NULL), some of which also duplicate
freshly-re-mirrored 'ohm' rows (UNIQUE), and some of which reference entries
that no longer exist.

Fix — a repair prologue at the top of 029 (no schema change):
- drop stale rows that duplicate an already-correctly-stamped row (keep the
  fresh copy) for the branch-keyed tables;
- re-derive each satellite's project_id from its entry (cached_rfcs, by slug);
- drop rows whose entry no longer exists (stale cache, rebuildable from gitea).
A no-op on clean/fresh deployments (empty or consistent satellites).

Validated against a snapshot of the live OHM DB: 029→032 apply cleanly, zero
NULL collection_id, FK check clean, watches/RFCs/branch_visibility preserved,
cached_branches 1397 → 1291 (−67 no-RFC, −39 dups). Fresh-install path
unchanged: existing 029 suite green + a new regression test for the
stale/dup/orphan shape. Full backend suite 547 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:02:53 -07:00
Ben Stull 9785782532 Merge pull request 'Discovery spec: configurable collection metadata (clean-doc tagging)' (#26) from docs/collection-metadata-design into main 2026-06-06 18:43:26 +00:00
Ben Stull 43a002c6aa Spec v0.1.6: §7.1 execution convention (one slice, one session, just-in-time)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 11:19:28 -07:00
Ben Stull 8ce3e5792d Spec v0.1.5: define Business Actors (§1.3) before Problem/Pain reference them
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 08:12:20 -07:00
Ben Stull 1be4a2edbf Spec v0.1.4: two-part structure — Business Context (§1) + Solution Proposal (§2)
- §1 Business Context holds the whole business lens (1.1–1.9), solution-agnostic
- §2 Solution Proposal introduces the chosen approach (and justifies build over
  a manual alternative); may be non-software in general
- Renumber product/engineering lenses to §§3–7

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 08:02:33 -07:00
Ben Stull 561cd73760 Spec v0.1.3: supersede per-type-surfaces; split personas; harvest patterns
- Supersede docs/design/2026-06-06-per-type-surfaces.md (banner added there);
  harvest validation seam, malformed-metadata flag, unknown-fields-ride-along,
  engine-unchanged invariant, N=1 document backcompat
- bdd coverage kept as a future per-type surface over a generic `ref` field
- Schema model = pure collection-config (not type-driven)
- SLICE-0 added: amend binding SPEC.md §22.4a (frontmatter→sidecar,
  type-driven→collection-configured, defer surfaces)
- Split personas: §6 Business Actors/Roles (solution-agnostic) + §10 Product
  Personas (mapped to business roles); renumber accordingly

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 07:34:15 -07:00
Ben Stull 3c910e89ab Revise spec to template: value-only summary, Pain Points, business framing
- Executive Summary → value-only (no solution)
- New §4 Pain Points (PP-1..PP-7)
- Business Outcomes → §5, restated as business outcomes (adoption/diversity),
  not solution outputs
- Business Use Cases rewritten as solution-agnostic actor-goal scenarios
- Renumber per the Solution Design template revision

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 07:15:35 -07:00
Ben Stull b7e23a01f8 Merge pull request '§22 S6 (last item): spec pass for per-type surfaces (§22.4a items 1 & 3)' (#25) from spec/s7-per-type-surfaces into main v0.46.0 2026-06-06 09:16:07 +00:00
Ben Stull 281dd29e62 §22 S6 (last item): spec pass for per-type surfaces (§22.4a items 1 & 3)
The per-type frontmatter schemas (item 1) and type-specific surfaces (item 3:
specification release-planning; bdd scenario/coverage views) were flagged at
v0.45.0 as wanting a discovery/spec pass first (they lacked BDD scenarios in
Part C). This is that pass — a design doc specifying the schemas, the surfaces,
their data model / API / frontend shape, BDD-style acceptance scenarios, and a
three-slice delivery plan (S7a schemas, S7b releases, S7c bdd surfaces), all
additive and engine-preserving per §22.4a. Doc-only; no code, no version bump.

- docs/design/2026-06-06-per-type-surfaces.md — the new spec pass.
- three-tier design doc S6 bullet: forward-pointer + S6 shipped/spec'd status.

Leaves implementation to the S7a–S7c coding slices; S7a (schemas) is unblocked,
S7b/S7c carry open product questions for the operator/discovery to settle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 02:15:45 -07:00
Ben Stull 1c17fecea3 Merge pull request '§22 S6: request-to-join + cross-collection inbox (§22.8) — v0.46.0' (#24) from feat/s6-join-requests into main 2026-06-06 09:12:00 +00:00
Ben Stull fcc3c84d76 §22 S6: request-to-join + cross-collection inbox (§22.8) — v0.46.0
Ships the request side of joining a gated scope, completing the §22.8 pair
(S4 shipped the invite half). A user who knows a project/collection exists
asks to join it naming a desired role; the request fans out to that scope's
Owners across the subtree (the cross-collection inbox, §22.11), who accept
(writing the memberships row via memberships.grant) or decline. Built by
analogy to §28 contribution_requests + the S4 memberships surface.

Backend
- migration 032: join_requests (scope_type ∈ {project,collection}, scope_id,
  requester, requested_role, message, status, granted_role); one-open-per
  (scope, requester) partial unique index. Additive — no rebuild.
- api_join_requests.py: GET join-target / POST join-requests / POST
  {id}/accept / {id}/decline under /api/scopes/{scope_type}/{scope_id}/.
  Accept grants via memberships.grant; the request POST does not require the
  scope be readable (that is how one joins a gated scope).
- notify: fan_out_join_request (subtree-Owner enumeration via
  _scope_owner_user_ids), notify_join_decided, 3 render_summary cases.
- auth.effective_role_at_scope — scope-grain twin of effective_scope_role,
  folding global → project for a project target.
- api_collections: viewer.can_request_join on the project + collection blocks.

Frontend
- api.js join verbs; JoinRequestModal; "Request to join" affordance in the
  collection directory + catalog footer; JoinRequestRow in the inbox.

Tests: backend test_join_requests_vertical (11) + test_migration_032 (5);
frontend api.joinrequests + CollectionDirectory cases. 546 backend / 36
frontend green.

Per docs/design/2026-06-05-three-tier-projects-collections.md Part E (S6) and
SPEC.md §22.8 / §22.11. Closes the request-to-join item flagged open at
0.45.0; per-type surfaces (§22.4a items 1 & 3) remain the last S6 item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 02:11:16 -07:00
Ben Stull e86fc65643 Discovery spec: configurable collection metadata (clean-doc tagging)
Solution Design for a generic per-collection metadata system — typed fields
(enum/tags/text) declared in .collection.yaml, per-entry values in a clean
<slug>.meta.yaml sidecar, schema-derived faceted left-pane filtering, and
single/bulk direct-commit tag/untag. Restores the retired BDD Release Planner's
corpus-annotation half inside the framework; release planning stays downstream.

Output of discovery session OHM-0079.0. Proposed as §23; flag for reconciliation
with §22 S6 type-modules at the SPEC merge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 01:52:26 -07:00
Ben Stull 014015014b Merge pull request '§22 S6: SPEC merge + per-collection model universe + type noun — v0.45.0' (#23) from feat/s6-three-tier-final into main 2026-06-06 08:44:25 +00:00
Ben Stull b392fa923c §22 S6: release v0.45.0 — two-project/multi-collection test + §20.4 changelog
The release wrap for the S6 scope shipped this slice (SPEC merge, per-collection
enabled_models, type-driven entry noun).

- test_s6_two_project_multicollection.py: an integrative pass proving the new
  per-collection knobs (§22.12 enabled_models, §22.4a noun) resolve independently
  per collection across two projects with no cross-project/-collection bleed.
- CHANGELOG.md 0.45.0: the §20.4 entry + upgrade-steps (migration 031 is
  additive + automatic; per-collection enabled_models and typed collections are
  optional). Notes the two OPEN S6 items carried to a follow-up slice (per-type
  surfaces; request-to-join + cross-collection inbox).
- VERSION + frontend/package.json → 0.45.0.

Gate: backend 530 passed, frontend 30 passed, build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 01:43:09 -07:00
Ben Stull 839404da0c §22 S6: type-driven entry noun (§22.4a) — backend source of truth + chrome
The displayed noun for an entry is a framework concept keyed on the collection's
immutable type — document→"RFC", specification→"Spec", bdd→"Feature" — not
deployment content. The chrome reads it from the API instead of hardcoding "RFC".

- collections.ENTRY_NOUN + entry_noun(type) (unknown type → generic "RFC").
- Surfaced as entry_noun on get_collection, list_collections items, the
  /api/deployment directory items, and GET /api/projects/:id.
- Frontend: Catalog reads entry_noun for the "+ Propose New <noun>" control;
  ProposeModal fetches the active collection's noun for its title + field copy.
- test_s6_entry_noun_vertical.py: map + API surfacing (collection + directory).

Scope note: this is §22.4a item (2) — terminology. The deeper type-module work
(item 1 per-type frontmatter schemas; item 3 specification release-planning +
bdd scenario/coverage surfaces) is flagged OPEN in the design doc and is handed
off to a follow-up spec+slice.

Backend 528 passed; frontend 30 passed + build green. Part of v0.45.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 01:35:57 -07:00
Ben Stull 79a27a946b §22 S6: per-collection enabled_models — the §22.12 model-universe chain
A collection's .collection.yaml may carry an enabled_models list that NARROWS
its project's universe, which narrows the deployment ENABLED_MODELS. Resolution
(extending §6.6/§6.7): funder ∩ per-entry models ∩ collection ∩ project, with
the operator providers as the ceiling.

- migration 031: additive collections.config_json (parallels projects.config_json).
- registry.parse_collection_manifest: read enabled_models into CollectionEntry.config
  (absent = inherit, present incl. [] = narrow; [] opts the collection out of AI);
  reject a non-list. _upsert_named_collection persists config_json. The default
  collection (from projects.yaml) leaves it NULL — it inherits the project.
- models_resolver: _scope_narrowed_universe narrows the operator universe by the
  entry's project then collection enabled_models; the funder universe is bounded
  by it too. A collection cannot widen its project (narrowing from the ceiling).
- collections.get_collection surfaces enabled_models; GET
  /api/projects/:id/collections/:cid returns it.
- test_s6_collection_models_vertical.py: 9 cases (parse, the narrowing chain
  incl. opt-out + cannot-widen, API surfacing).

Backend: 525 passed. Per SPEC §22.12. Releases as part of v0.45.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 01:30:16 -07:00
Ben Stull 26f3680197 §22 S6: merge three-tier model into SPEC.md + registry format in DEPLOYMENTS
Write the canonical §22 (deployment → project → RFC collection) into the
binding spec, applying Part A of the design doc and the Part D amendments in
place. Lands the S3 keystone reinterpretation of §B.1/§B.3: a plain granted
account is a granted account, not a global write role; "global RFC Contributor"
is an explicit memberships(scope_type='global') grant; the implicit-public
write baseline is grandfathered onto the migration-seeded default collection
only (N=1 preserved).

- SPEC.md §22.1–§22.14: tiers + isolation, the registry + .collection.yaml
  manifests, content-repo-per-project, per-collection slug identity, collection
  type/initial_state/unreviewed, two-tier visibility (narrow-only), the unified
  {owner, contributor} role vocabulary at {global, project, collection}, the
  four-layer most-permissive union, discovery/joining (invite + request-to-join),
  runtime branding, /p/<project>/c/<collection>/ routing, one inbox,
  per-collection model universe, the default-project+collection migration, and
  §22.14 consolidating the §§1–21 amendments.
- Forward-pointer amendment notes (house style) at §1, §2, §5, §6 routing
  readers to §22.
- docs/DEPLOYMENTS.md: the registry (REGISTRY_REPO / projects.yaml) and
  .collection.yaml manifest formats, the N=1 default-project+collection upgrade,
  and in-app create-project/collection.

Per docs/design/2026-06-05-three-tier-projects-collections.md Parts A/B/D/E (S6).
Docs-only; no code or migration. Releases as part of v0.45.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 01:21:39 -07:00
Ben Stull b0737380cd §22 S5: in-app create-project + global directory — v0.44.0 (@S5) (#22) 2026-06-06 08:03:12 +00:00
Ben Stull 33212c71e4 §22 S5: in-app create-project + global-directory empty states (@S5 C3.1–C3.2)
Adds the global-Owner create-project action and the role-aware deployment
directory, completing slice S5 of the three-tier refactor (release v0.44.0,
minor/non-breaking — no migration). Per
docs/design/2026-06-05-three-tier-projects-collections.md Part E (S5),
Part C.3 (C3.1–C3.2).

Backend:
- auth.can_create_project — global-Owner gate (deployment owner/admin or an
  explicit scope_type='global' Owner grant).
- bot.create_project — provision the Gitea content repo (seed README so main
  exists), read+append+commit projects.yaml in the registry repo, audit-log
  (create_project). The bot stays the only git writer (§1).
- POST /api/projects (api_deployment) — global-Owner gated; validates the id
  (slug, not 'default'), name, type, visibility, content_repo; commits via the
  bot, then re-mirrors the registry so projects + default collection rows flow
  from git (§22.2). GET /api/deployment gains viewer.can_create_project and
  default_project_readable. make_router now takes gitea + bot.

Frontend:
- api.createProject; DeploymentProvider surfaces viewer + defaultProjectReadable
  + refresh; DeploymentLanding redirects into the default only when readable
  (gated/absent default falls through to the directory, no 404 bounce).
- Directory.jsx role-aware empty states (C3.1 "Create your first project" CTA;
  C3.2 "Nothing has been shared with you yet") + Owner-only "New project"
  control + CreateProjectModal.

Tests: backend test_create_project_vertical.py (vertical + gates + the
deployment empty-state signals); frontend Directory.test.jsx empty-state cases.

Also: ignore the session-local .superpowers/ tooling dir.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 01:02:16 -07:00
Ben Stull 2696e64ff5 Merge pull request '§22 S4: invitation surfaces + role-aware empty states — v0.43.0 (@S4)' (#21) from feat/s4-invitation-surfaces-empty-states into main 2026-06-06 07:31:01 +00:00
Ben Stull ff54632657 §22 S4: release v0.43.0 — invitation surfaces + role-aware empty states (@S4)
Bump VERSION + frontend/package.json to 0.43.0, add the CHANGELOG entry
(minor, non-breaking — additive endpoints + UI, no migration, no change to
existing authz outcomes), and mark Part E slice S4 shipped in the design doc.

Completes @S4 (C2.1–C2.7 invitation + C3.3–C3.5 role-aware empty states).
Next: S5 (in-app create-project + the global directory, C3.1–C3.2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 00:30:12 -07:00
Ben Stull 93cf506059 §22 S4: invitation modal + role-aware empty states (@S4 C.2, C.3)
The frontend surfaces for the S4 backend (Part E S4 / Part C.2, C.3):

- ScopeMembersModal — the Owner-only scope-role invitation surface (sibling of
  the per-RFC InvitationsModal): grant {owner, contributor} by email at the
  project or a single collection, with a scope picker bounded to the inviter's
  reach (no parent-grant-child-exclude option, C.2.5), plus a current-members
  list with revoke. Opened from the directory's owner-only "Members" control;
  contributors never see it (C.2.4).
- CreateCollectionModal — surfaces the S2 create-collection endpoint (was
  UI-less), gated on the viewer's can_create_collection capability.
- CollectionDirectory — reads the new `viewer` capability block to render the
  role-aware empty states: a project Owner sees "Create your first collection"
  (C3.3); a contributor without create rights sees the bare empty directory
  (C3.4); an Owner with management reach sees the "Members" control.
- Catalog — the empty collection shows "Propose the first entry" to a viewer
  who may contribute here (C3.5), and the propose control is gated on the
  collection's can_contribute flag (anon keeps the sign-in prompt, S2).
- api.js — getCollection, listScopeMembers, grantScopeMember, revokeScopeMember.

Frontend builds clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 00:27:49 -07:00
Ben Stull c9fd1c535e §22 S4: scope-role invitation backend + capability flags (@S4 C.2)
The Owner-only scope-role grant surface (Part E S4 / Part C.2). An Owner
grants {owner, contributor} at a scope their reach covers — the project or
one collection within it — to an existing account looked up by email; the
grant writes a `memberships` row immediately and §15-notifies the grantee
(direct grant, no accept round-trip — the C.2 scenarios name existing
accounts and write the row directly).

- auth.can_invite_at_project / can_invite_at_collection — the Owner-reach
  invite gates (is_project_superuser / is_collection_superuser).
- memberships.py — grant (with the C.2.6 broader-supersedes-narrower prune,
  preserving a stronger child grant — no negative override), revoke, list,
  user_by_email.
- api_memberships.py — GET/POST/DELETE /api/projects/:id/members, the single
  POST keying on optional collection_id so the invite UI's one control maps
  to one endpoint; reach bounded by the inviter's Owner reach (C.2.3);
  contributors refused (C.2.4); a pending grantee's row is recorded but
  confers no write (C.2.7, the §6 floor).
- notify.notify_scope_role_granted + render_summary — the §15 personal-direct
  notification naming the project and role.
- api_collections — surface viewer capabilities (can_create_collection,
  can_invite, can_contribute, role) on the project/collection GETs to drive
  the C.3 role-aware empty states.

Acceptance: test_s4_invitations_vertical.py covers C.2.1–C.2.7 over the HTTP
surface + resolver, plus the C.3 (@S4) capability flags. Full suite green
(504 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 23:47:28 -07:00
benstull e6bd69f132 Merge pull request '§22 S3: scope-role enforcement + collection-grain visibility — v0.42.0 (@S3)' (#1) from feat/s3-scope-roles-collection-visibility into main 2026-06-06 01:08:54 +00:00