feat: capture classification taxonomy + per-wing/repo tags (#50, capture 49a) #56

Merged
mathias merged 1 commits from feat/capture-classification into main 2026-06-22 21:31:18 +00:00
Owner

Implements #50 (capture sub-issue 49a) — the I1-prerequisite classification taxonomy + per-wing/repo tagging. Pure prerequisite, no dependency on the other sub-issues.

What landed

New package ingestion/internal/classification/:

  • TaxonomyLevel: public < internal < confidential, ordered so "stricter wins" (spec §4.1 model C) is a plain max via Stricter(a, b).
  • ParseLevel — case-insensitive; unknown token → error (no silent coercion).
  • Config.Load(brainDir) — reads an optional classification.yaml (wings: / repos: maps). Absent file → defaults-only, no error. An unparseable level in the file → hard error (a classification source the server can't trust fails loud).
  • Config.Derive(Target) — the function the use-case calls per item; Wing(name) / Repo(name) are the per-kind helpers.
  • Defaulting — explicit tag wins → client-* → confidential → hyperguild/homelab → internal → everything else → confidential (fail safe to strictest).

Design decision: central classification.yaml, not the alternatives

The issue floated frontmatter-on-_index.md, a gitea repo topic, or a server-side map. I chose a central classification.yaml at the brain root for three reasons:

  1. No gitea dependency — classifying a repo from a server-readable map needs no live Gitea client, so #50 has no code dependency on the tracker work (#52), and the two dependency-graph roots (#50, #51) stay buildable in parallel as the epic intends.
  2. Auditable in one place (I2/I5) — one Flux-reconcilable file shows every classification.
  3. Frontmatter would be clobberedbrain.BuildWingIndex regenerates a wing's _index.md; durable classification metadata there is fragile.

The file is deployed into the brain dir (runtime data), not committed here. Format is documented in the spec edit + brain note.

Fail-safe is the load-bearing property

The I1 gate (#53) is only as strong as the classification it reads. A missing/unknown tag resolving to confidential (never permissive) is what keeps the gate from being theatre. Covered explicitly: TestWingDefaulting ("" and unknown → confidential), TestRepoDefaulting, TestDeriveUnifiedTarget.

Acceptance criteria

  • Taxonomy levels defined + ordered; documented (brain note + referenced from the spec)
  • Per-wing classification tag mechanism implemented and readable by the server
  • Per-repo classification tag mechanism implemented and readable by the server
  • deriveClassification (Config.Derive/Wing/Repo) returns the level for a tagged target; untagged → confidential (fail safe)
  • Defaulting rules applied (client-* → confidential; homelab/hyperguild → internal)
  • Unit tests incl. the untagged-fails-safe path (+ stricter-wins, explicit-tag-overrides, bad-level-in-file, case-insensitive)
  • task check green (lint 0 issues, vet clean, -race tests green)

Docs

  • Spec §4.1 updated with an "Implemented (#50)" note pointing at the package + defaults.
  • Brain: wiki/hyperguild/decisions/capture-classification-taxonomy.

Not auto-merged — review + merge from claude.ai.

Closes #50

🤖 Generated with Claude Code

Implements #50 (capture sub-issue 49a) — the I1-prerequisite classification taxonomy + per-wing/repo tagging. Pure prerequisite, no dependency on the other sub-issues. ## What landed New package `ingestion/internal/classification/`: - **Taxonomy** — `Level`: `public < internal < confidential`, ordered so "stricter wins" (spec §4.1 model C) is a plain `max` via `Stricter(a, b)`. - **`ParseLevel`** — case-insensitive; **unknown token → error** (no silent coercion). - **`Config.Load(brainDir)`** — reads an optional `classification.yaml` (`wings:` / `repos:` maps). **Absent file → defaults-only, no error.** An unparseable level *in* the file → hard error (a classification source the server can't trust fails loud). - **`Config.Derive(Target)`** — the function the use-case calls per item; `Wing(name)` / `Repo(name)` are the per-kind helpers. - **Defaulting** — explicit tag wins → `client-*` → confidential → `hyperguild`/`homelab` → internal → **everything else → confidential (fail safe to strictest)**. ## Design decision: central `classification.yaml`, not the alternatives The issue floated frontmatter-on-`_index.md`, a gitea repo topic, or a server-side map. I chose a **central `classification.yaml` at the brain root** for three reasons: 1. **No gitea dependency** — classifying a *repo* from a server-readable map needs no live Gitea client, so #50 has **no code dependency on the tracker work (#52)**, and the two dependency-graph roots (#50, #51) stay buildable in parallel as the epic intends. 2. **Auditable in one place** (I2/I5) — one Flux-reconcilable file shows every classification. 3. **Frontmatter would be clobbered** — `brain.BuildWingIndex` regenerates a wing's `_index.md`; durable classification metadata there is fragile. The file is deployed *into the brain dir* (runtime data), not committed here. Format is documented in the spec edit + brain note. ## Fail-safe is the load-bearing property The I1 gate (#53) is only as strong as the classification it reads. A missing/unknown tag resolving to **confidential** (never permissive) is what keeps the gate from being theatre. Covered explicitly: `TestWingDefaulting` (`""` and unknown → confidential), `TestRepoDefaulting`, `TestDeriveUnifiedTarget`. ## Acceptance criteria - [x] Taxonomy levels defined + ordered; documented (brain note + referenced from the spec) - [x] Per-wing classification tag mechanism implemented and readable by the server - [x] Per-repo classification tag mechanism implemented and readable by the server - [x] `deriveClassification` (`Config.Derive`/`Wing`/`Repo`) returns the level for a tagged target; **untagged → confidential (fail safe)** - [x] Defaulting rules applied (`client-*` → confidential; homelab/hyperguild → internal) - [x] Unit tests incl. the untagged-fails-safe path (+ stricter-wins, explicit-tag-overrides, bad-level-in-file, case-insensitive) - [x] `task check` green (lint 0 issues, vet clean, `-race` tests green) ## Docs - Spec §4.1 updated with an "Implemented (#50)" note pointing at the package + defaults. - Brain: `wiki/hyperguild/decisions/capture-classification-taxonomy`. Not auto-merged — review + merge from claude.ai. Closes #50 🤖 Generated with [Claude Code](https://claude.com/claude-code)
mathias added 1 commit 2026-06-22 20:42:25 +00:00
feat(classification): data-sensitivity taxonomy + per-wing/repo tags (#50)
CI / Lint / Test / Vet (pull_request) Successful in 13s
CI / Mirror to GitHub (pull_request) Has been skipped
98cfae595c
Implements the I1-prerequisite from #49/#50: the classification taxonomy
and the per-wing / per-repo tagging the capture server reads to derive a
target's sensitivity.

- Levels public < internal < confidential, ordered so "stricter wins"
  (spec §4.1 model C) is a plain max via Stricter().
- Tags read from an optional classification.yaml at the brain root
  (wings:/repos: maps). Absent file → defaults-only, not an error.
- Defaulting: client-* → confidential; hyperguild/homelab → internal;
  everything else → confidential. Fail-safe-to-strictest is the
  load-bearing property: a missing tag never silently downgrades.
- Config.Derive(Target) is the function the use-case calls; Wing/Repo
  are the per-kind helpers. ParseLevel rejects unknown tokens; a bad
  level in the config file is a hard load error.

Central classification.yaml (not _index.md frontmatter, not gitea repo
topics): classifying a repo needs no live Gitea client, so #50 has no
dependency on the tracker work (#52); it's auditable in one place; and
it avoids BuildWingIndex clobbering a wing's regenerated _index.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mathias merged commit f6bf9b5f57 into main 2026-06-22 21:31:18 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mathias/hyperguild#56