Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
13 changes: 13 additions & 0 deletions .agents/wisdom/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@
- Free tier has rate limits (35 min cooldown). After hitting the limit, sleep and retry.
- CodeRabbit finds things the pre-push self-review misses (security lens, async assertion gaps). Do not skip it.

## CodeRabbit SUCCESS with no review output requires verification

When the CodeRabbit status check shows SUCCESS but no review summary or inline
comments are posted, do not assume the review ran or that it was rate-limited.
Check whether the latest reviewable commit was covered and whether the result
was rate-limited, unavailable, or skipped incrementally.

**Always**: After triggering `@coderabbitai review`, wait 2-3 minutes, then:
1. Check PR comments for a review summary
2. Fetch inline comments: `gh api --paginate repos/<owner>/<repo>/pulls/<num>/comments`
3. If no valid review evidence exists, report the review as unavailable and
require an explicit human decision before proceeding

## Pre-merge three-step gate

From `docs/engineering-workflow.md`:
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ dist/
.DS_Store
.pi-subagents/
.scratch/
video
.playwright-cli/
.playwright-mcp/
.worktrees/
138 changes: 124 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ The demo is an authorization decision gallery, not an authentication or administ
It lets you switch between:

- **Super Admin** — can manage the demo policy and inspect the schedule controls;
- **Admin** — has full car actions but cannot manage policy;
- **Admin** — can read, create, and update cars; scheduled delete access is available only inside the configured work window, and policy management is still locked;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
- **Support** — can read and create, has a direct denial for delete, and can receive a live direct grant for update from the Super Admin policy editor.

The demo shows:
Expand Down Expand Up @@ -190,7 +190,7 @@ bun run build

To test the browser demo, run bun run dev, then exercise the three principals and verify:

1. Admin can perform the car actions but cannot open policy controls.
1. Admin can read, create, and update cars; scheduled delete works inside the configured window and is denied outside it, while policy controls remain locked.
2. Support can read and create, but delete is denied with matching-denial.
3. Super Admin can grant Support cars.update and the decision changes without a page reload.
4. The schedule allows access inside its window and returns outside-schedule outside it.
Expand All @@ -212,22 +212,132 @@ It does not currently:

Those responsibilities stay with the host application or optional integrations. A production application should obtain a trusted principal from its authentication layer, resolve the required authorization facts through its adapters, call Mizan, and enforce the result on the server or API boundary.

## Built with Codex
## Inspiration

Mizan was developed for OpenAI Build Week using Codex and GPT-5.6.
Authorization is rebuilt inside almost every application, even though the
underlying questions are often the same: does this principal have this
permission, does a denial override a grant, is the access scoped or temporary,
and what should happen when the application's data model is different?

The human builder owned the product direction, architectural decisions, acceptance criteria, trade-offs, and final review. Codex and supporting AI agents were used to:
We wanted a small open-source decision layer that could be reused across
projects without forcing a database schema, ORM, authentication provider, or
cache. The central idea became simple: adapters provide facts; Mizan makes the
final authorization decision.

- turn the authorization concept into a small, testable v0.1 architecture;
- design the source/adapter boundary and decision model;
- implement the core evaluator and memory adapter;
- create tests for grants, denials, scopes, temporal windows, schedules, and source behavior;
- review changes, find edge cases, and improve documentation;
- build the interactive decision demo used for evaluation.
## What it does

Mizan is a runtime-neutral, TypeScript-first authorization library. It
evaluates:

- role-derived and direct grants;
- direct denial overrides, with denial taking precedence;
- exact permissions and patterns such as `files.*` and `*`;
- global and scoped facts;
- absolute validity windows with `startsAt` and `expiresAt`;
- recurring weekly and date-specific schedules;
- facts from named sources and source plans;
- explainable `allow` or `deny` decisions with stable reason codes.

The interactive demo makes these decisions visible with Super Admin, Admin,
and Support principals. It demonstrates policy management, a scheduled Admin
delete permission, a Support denial override, a controllable evaluation clock,
and protected actions that call the real Mizan evaluator before changing demo
state.

Mizan is authorization, not authentication. The host application remains
responsible for users, sessions, JWTs, cookies, persistence, revocation,
caching, and server-side enforcement.

The important design decision is that AI-assisted implementation does not move application data ownership into Mizan: adapters provide facts, while Mizan remains the decision layer.
## How we built it

The required Codex session information is provided in the Devpost submission rather than committed to this repository.
Mizan is a fixed-version TypeScript monorepo. The core package owns the
decision algorithm and source contracts, while the memory package provides a
small reference adapter for tests and examples. This keeps the core
independent from Prisma, Drizzle, PostgreSQL, SQLite, Redis, or any other
storage choice.

### Codex and GPT-5.6 collaboration

Mizan was developed for OpenAI Build Week using Codex and GPT-5.6 as the
planning, architecture, and review layer. The human builder owned the product
direction, architectural decisions, acceptance criteria, trade-offs, and final
review.

ChatGPT/GPT-5.6 and Codex were used to:

- turn the authorization concept into a small, testable v0.1 architecture;
- design the source/adapter boundary and decision model;
- break the work into milestones and focused implementation tasks;
- define acceptance criteria and test scenarios;
- inspect changes, find edge cases, and review the resulting behavior;
- guide the documentation and interactive demo story.

Coding-capable worker agents/models carried out the implementation tasks under
those decisions. This was a deliberate separation: stronger reasoning models
focused on architecture and review, while specialized coding agents handled
the repository changes. Codex was the shared engineering workspace and
workflow used to coordinate that collaboration; it is not presented as the
sole author of the implementation.

The important boundary is that AI-assisted implementation does not move
application data ownership into Mizan: adapters provide facts, while Mizan
remains the decision layer.

The required Codex session information is provided in the Devpost submission
rather than committed to this repository.

## Challenges we ran into

The main challenge was balancing useful defaults with freedom for existing
applications. A library that owns the schema is easy to start with but quickly
becomes difficult to reuse, so we kept storage and authentication outside the
core and made the adapter boundary explicit.

We also had to make precedence and time behavior visible rather than hiding it
inside a boolean helper. A direct denial must remain stronger than a role grant,
and a scheduled grant must be evaluated against one consistent clock across the
sidebar, table actions, and actual mutation path. Finally, the multi-agent
workflow required tests, review gates, and human decisions so that speed did not
replace correctness.

## Accomplishments that we're proud of

- A small reusable authorization core with no ORM or authentication coupling.
- A memory adapter that acts as a reference for custom adapters.
- A fixed-version monorepo structure ready for future integrations.
- Explainable decisions with stable denial reasons instead of opaque booleans.
- Coverage for role grants, direct grants, denial precedence, scopes, temporal
windows, schedules, and source behavior.
- An interactive browser demo that shows the decision layer working end to end.
- A documented AI-assisted development process using Codex and GPT-5.6.

## What we learned

We learned that the most reusable abstraction is not a database model but a
clear capability boundary: the adapter translates application data into facts,
and the authorization engine decides. We also reinforced that UI visibility is
only a user-experience concern; every protected operation must be checked again
at the server or API boundary.

Working with AI agents also made the engineering process itself important.
Small milestones, explicit acceptance criteria, automated tests, adversarial
review, and a final human decision made the collaboration much more reliable
than asking one model to generate an entire system without checkpoints.

## What's next for Mizan

The next steps are driven by real applications rather than by trying to solve
every authorization problem at once:

- keep the v0.1 core stable while improving adapter ergonomics and examples;
- add useful, tested integrations for common database and framework setups;
- support composed sources for database facts, cache lookups, and revocation
workflows without moving those responsibilities into the core;
- explore resource-aware rules and a small ABAC extension for ownership and
tenant-aware decisions;
- add optional tooling for policy import, synchronization, audit events, and
administration;
- publish stable fixed-version releases as the API matures toward v1.

## Hackathon testing path

Expand All @@ -252,4 +362,4 @@ Mizan is an early open-source release focused on a dependable decision core and

## License

Mizan is released under the MIT License. See LICENSE.
Mizan is released under the MIT License. See LICENSE.
20 changes: 12 additions & 8 deletions examples/interactive-decision-demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Then open **http://localhost:3000** (or whatever port your server uses) in a bro

| Concept | Implementation |
|---------|---------------|
| **Three principals** | Super Admin (full access + policy management), Admin (full cars access), Support (restricted) |
| **Three principals** | Super Admin (full access + policy management), Admin (read/create/update plus schedule-controlled delete), Support (restricted) |
| **Protected actions** | Every cars-table click calls `decide()` through Mizan before mutating state |
| **Denial reasons** | `matching-denial` for the delete override, `no-grant` for missing update permission |
| **Decision banner** | Current decision shown above the fold: actor, action, ALLOW/DENY, reason |
Expand All @@ -45,16 +45,20 @@ Then open **http://localhost:3000** (or whatever port your server uses) in a bro
| `cars.read` | ✅ | ✅ | ✅ |
| `cars.create` | ✅ | ✅ | ✅ |
| `cars.update` | ✅ | ✅ | ❌ `no-grant` |
| `cars.delete` | ✅ | ✅ | ❌ `matching-denial` |
| `cars.delete` | ✅ | ✅ (schedule) | ❌ `matching-denial` |
| `manage-policy` | ✅ | ❌ `no-grant` | ❌ `no-grant` |
| `reports.read` | ✅ (schedule) | ❌ `no-grant` | ❌ `no-grant` |

## How the schedule works

Super Admin can enable/disable the schedule restriction on `reports.read` and
adjust the UTC business-hours window. A controllable evaluation clock advances
or rewinds time — within hours the permission allows, outside it returns
`outside-schedule`. Admin and Support cannot modify these settings.
Super Admin can enable/disable the schedule restriction on Admin's `cars.delete`
and adjust the UTC business-hours window. A controllable evaluation clock
advances or rewinds time — within hours the permission allows, outside it
returns `outside-schedule`. Admin and Support can see the schedule status but
cannot modify the settings.

The schedule restriction is enforced by the real Mizan `cars.delete` decision
evaluated at the demo clock time. Changes to the schedule are separately gated
by the real Mizan `manage-policy` decision, so non-Super Admin users are blocked
by the authorization engine, not just by disabled UI controls.

## Stack

Expand Down
66 changes: 29 additions & 37 deletions examples/interactive-decision-demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,42 +54,7 @@ <h3 class="policy-block-title">Deny overrides</h3>
</label>
</div>
<div class="policy-block">
<h3 class="policy-block-title">Temporal schedule</h3>
<p class="schedule-note">Hypothetical: <code>reports.read</code> evaluated against a configurable schedule.</p>
<label class="policy-toggle" data-permission="schedule">
<input type="checkbox" id="toggle-schedule" checked />
<span class="toggle-label">Enforce schedule on <code>reports.read</code></span>
</label>
<div class="schedule-editor">
<div class="hour-input-group">
<label class="hour-label" for="schedule-start-h">Start (UTC)</label>
<div class="hour-field">
<input type="number" id="schedule-start-h" class="hour-input" min="0" max="23" value="9" aria-label="Start hour" />
<span class="hour-sep">:</span>
<input type="number" id="schedule-start-m" class="hour-input minute" min="0" max="59" value="0" step="15" aria-label="Start minute" />
</div>
</div>
<div class="hour-input-group">
<label class="hour-label" for="schedule-end-h">End (UTC)</label>
<div class="hour-field">
<input type="number" id="schedule-end-h" class="hour-input" min="0" max="23" value="17" aria-label="End hour" />
<span class="hour-sep">:</span>
<input type="number" id="schedule-end-m" class="hour-input minute" min="0" max="59" value="0" step="15" aria-label="End minute" />
</div>
</div>
</div>
<div class="clock-controls">
<span class="clock-label">Evaluation clock</span>
<div class="clock-row">
<span id="clock-display" class="clock-time" aria-live="polite">—</span>
<div class="clock-buttons">
<button id="clock-dec" class="clock-btn" type="button">−1h</button>
<button id="clock-inc" class="clock-btn" type="button">+1h</button>
<button id="clock-reset" class="clock-btn" type="button">reset</button>
</div>
</div>
<div id="schedule-result" class="schedule-result" aria-live="polite">—</div>
</div>
<p class="schedule-note">Schedule controls are in the toolbar above, visible to all principals. Only Super Admin can modify them. Controls Admin's <code>cars.delete</code> permission.</p>
</div>
</div>

Expand Down Expand Up @@ -131,7 +96,34 @@ <h3 class="policy-block-title">Temporal schedule</h3>
</div>
</section>

<!-- Decision strip (main column, directly below controls) -->
<!-- Schedule bar (main column, directly below controls) -->
<div id="schedule-bar" class="schedule-bar">
<label class="schedule-toggle-compact" title="Enable/disable schedule enforcement">
<input type="checkbox" id="toggle-schedule" checked />
<span>Schedule <code>cars.delete</code></span>
</label>
<div class="schedule-hours">
<input type="number" id="schedule-start-h" class="hour-input" min="0" max="23" value="9" aria-label="Start hour" />
<span class="hour-sep">:</span>
<input type="number" id="schedule-start-m" class="hour-input minute" min="0" max="59" value="0" step="15" aria-label="Start minute" />
<span class="schedule-dash">—</span>
<input type="number" id="schedule-end-h" class="hour-input" min="0" max="23" value="17" aria-label="End hour" />
<span class="hour-sep">:</span>
<input type="number" id="schedule-end-m" class="hour-input minute" min="0" max="59" value="0" step="15" aria-label="End minute" />
<span class="schedule-tz">UTC</span>
</div>
<div class="schedule-clock">
<span id="clock-display" class="clock-time" aria-live="polite">—</span>
<div class="clock-buttons">
<button id="clock-dec" class="clock-btn" type="button" title="Decrease clock by 1 hour">−1h</button>
<button id="clock-inc" class="clock-btn" type="button" title="Increase clock by 1 hour">+1h</button>
<button id="clock-reset" class="clock-btn" type="button" title="Reset clock to initial time">reset</button>
</div>
</div>
<div id="schedule-result" class="schedule-result" aria-live="polite">—</div>
</div>

<!-- Decision strip (main column, directly below schedule bar) -->
<div id="current-decision" class="decision-strip" aria-live="polite" role="status" hidden>
<span class="decision-actor-label"></span>
<span class="decision-verb"></span>
Expand Down
Loading
Loading