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
16 changes: 14 additions & 2 deletions .forge/features/hacker-management/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,27 @@ the readiness signal that slice computed finally means something.

## Users / actors

- **Officers** — the only actors here. They read the roster, change statuses,
blacklist, and un-blacklist.
- **Hacker readers** — users granted Read Hackers can browse, search, filter,
and open applications, including their event attendance. They cannot edit.
- **Hacker editors** — users granted Edit Hackers can also correct applications,
adjust points, change statuses singly or in bulk, and delete applications.
- **Officers** — retain full access, including blacklist information and controls.
- **Applicants** — never see this screen. They experience it only as the mail a
transition sends. They are the reason the guards exist.

Not actors in this slice: applicants applying (the hack sites own that, and the
SDK carrying it is two slices out), and anyone checking in (moved to the event
slice).

### Delegated access (approved 2026-09-06)

Read Hackers and Edit Hackers must work without officer access. Editors also
have read access. The Hackers navigation and direct links use the same policy.
Readers see application details and filters without selection or write controls.
Blacklist information, filtering, and changes remain officer-only. Editors cannot
delete a blacklisted application to clear that restriction; they must ask an
officer. Configuration and event check-in permissions remain separate.

## User-visible interface

**`/admin/hackathon/[id]/hackers`** — the roster for one hackathon, reached from
Expand Down
43 changes: 26 additions & 17 deletions .forge/features/hacker-management/srd.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,32 @@ From `docs/agentic-development/forge-engineering-principles.md`:

## Access policy

Every procedure is `permProcedure` asserting officer, matching the
configuration router it sits beside.

- **Unauthenticated / non-officer:** no access; the route redirects.
- **Officer:** full read and write.

Two notes that are not obvious:

- **`READ_HACKERS` exists and is deliberately not accepted.** A read-only tier
would be defensible for the roster, but the roster carries applicant PII
(email, school, phone via the hacker record) and every write on this screen is
officer-only anyway. Splitting the tier is a decision for whoever needs it,
not a guess to make now.
- **The blacklist flag must never leave the officer tier.** It is a judgement
about a person recorded where they cannot see it. No procedure reachable by a
member or by an applicant may return it, and the SDK slice must not expose it
when it starts serving hacker data to external sites.
Delegated hacker access was approved on 2026-09-06 and supersedes the original
officer-only policy. Existing permission bits are reused; no migration is needed.

- `READ_HACKERS` or `EDIT_HACKERS`: roster options, filters, counts, selection
survival, application details, and the existing hacker event-attendance read.
- `EDIT_HACKERS`: profile corrections, point adjustments, single/bulk status
changes (including preview), and application deletion.
- `IS_OFFICER`: overrides the above and is still required for blacklist access
and hackathon configuration. Do not broaden the platform configuration guard.
- No hacker capability: no navigation, page access, or API access.

The API redacts blacklist fields to null for non-officers, rejects blacklist
filters (including false), and replaces blacklist skip reasons/errors with a
request for officer review. Existing transition safeguards still apply. Deleting
a blacklisted application requires an officer, checked under the attendee lock.
Officer responses retain their existing shape and values.

Blade must gate its admin layout, navigation, and page consistently. It passes
read/edit/officer capabilities to the roster and detail controls. Read-only
users cannot select rows or see mutations. The event-attendance read uses hacker
read access; general event administration and check-in keep their existing gates.

Validation covers the actual routers, role unions/revocation, read-only UI, and
blacklist redaction/filtering. Rollout is a normal application deployment;
rollback is reverting the code. No persisted data, permission indices, email
delivery mechanics, or dependencies change.

## Architecture / data flow

Expand Down
38 changes: 37 additions & 1 deletion .forge/features/hacker-management/status.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,43 @@
Phase: **implemented** — whole bundle approved 2026-08-03 and built. Awaiting
`forge-review` and the owner's UI pass.

## Context
## Delegated hacker permissions — 2026-09-06

Phase: PR open; awaiting CI and review. Branch: `codex/blade-hacker-permissions`.

- Issue: [#538](https://github.com/KnightHacks/forge/issues/538).
- PR: [#539](https://github.com/KnightHacks/forge/pull/539).
- Rebased onto `main` before publishing to exclude the unrelated navigation
changes in PR #537. The permission fix applied without conflicts; validation
passed again on this standalone branch (194 API tests, 34 Blade tests, and
seven browser tests).

- Owner approved making Read Hackers functional without officer status, with
Edit Hackers controlling writes and blacklist/configuration remaining officer-only.
- Scope: Blade hacker navigation/page/controls, hacker API guards and responses,
and the hacker detail's event-attendance read. No schema or dependency changes.
- Regression proof: the new API matrix failed 31 cases before implementation.
The completed API checks pass 194 tests across hacker access, real database
guards, hackathon configuration access, event access, and role permissions.
- Blade: 34 targeted tests pass. All seven hacker-management browser tests pass,
including readers at 1440px and 320px, the legacy link, filtering/search,
details and attendance, editor controls, unauthorized redirects, and existing
officer flows. Roster/detail screenshots were inspected at both widths.
- Database and browser checks used disposable local databases, dropped afterward.
Status-mail tests enqueue only in the disposable database; no delivery worker runs.
- `pnpm format`, `pnpm lint`, `pnpm typecheck`, and
`pnpm analyze:react:changed` pass. Lint retains the repository's existing
warnings. The root typecheck covers shared API consumers.
- No schema, permission-bit layout, dependency, or persistent environment changes.
`pnpm build` was attempted before publishing and failed while collecting page
data: Guild lacks `JUDGING_ACCESS_SECRET` and `NEXT_PUBLIC_BLADE_URL`; the 2026
app lacks `KHIX_HACKER_PORTAL_CLIENT_ID` and `KHIX_HACKER_PORTAL_ORIGIN`.
`pnpm --filter=@forge/blade build` also failed collecting `/judge/end` due to
missing `JUDGING_ACCESS_SECRET` and `NEXT_PUBLIC_BLADE_URL`.
No environment values were changed to bypass this. Deployment is not performed.
- Screenshot files are excluded from the repository at the owner's request.

## Original context

The slice after hackathon configuration. That slice made per-status mail
officer-editable; nothing sends it, and `isConfigured` is computed and read by
Expand Down
22 changes: 18 additions & 4 deletions .forge/features/hacker-management/test-cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,13 +273,27 @@ Expected: the total is unchanged by all of them.

### TC-NEG-001: Access (AC — SRD access policy)

Setup: unauthenticated; a logged-in non-officer; an officer.
Setup: unauthenticated; no hacker permissions; Read Hackers only; Edit Hackers
only; both hacker permissions; an officer.

Action: call every procedure in the router.

Expected: the first two are refused for every procedure; the officer succeeds.
Asserted against the router's actual procedure list so a procedure added later
without a guard fails this test rather than slipping through.
Expected: the first two are refused for every procedure. Readers pass every read
and cannot mutate, including bulk preview. Editors pass reads and writes except
blacklist management. Officers pass all guards. Assert against the router's actual
procedure list so new procedures need an access decision. Permissions granted
through separate roles combine; revoking them takes effect on the next request.

Blade: a reader can reach `/admin/hackers` and the legacy per-hackathon link,
switch hackathons, search/filter, and open details including event attendance.
There are no selection, status, edit, points adjustment, delete, or blacklist
controls. Editors see ordinary write controls; only officers see configuration
and blacklist controls. A signed-in user without hacker access is redirected.

For readers and editors, roster/detail responses redact blacklist values to null.
Blacklist filters, including `false`, fail before roster/count/selection reads.
Bulk responses never name the blacklist to editors. Status changes still reject
ineligible applicants, and an editor cannot delete a blacklisted application.

### TC-NEG-002: A blacklisted applicant cannot be accepted (AC-014)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ type SendingStatus = keyof typeof HACKER_STATUS_LABELS;
* `undefined`, which React renders as nothing. A blank explanation on the one
* list whose job is saying who was *not* mailed is worse than an ugly one.
*/
export function skipLabel(reason: string) {
export function skipLabel(reason: string | null) {
if (reason === null) return "Requires officer review";
// `hasOwn`, not `in`: `in` walks the prototype, so `"toString"` would pass the
// guard and return a function, which React refuses to render — an error
// boundary in place of the fallback this helper exists to provide.
Expand Down
Loading
Loading