Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
12 changes: 12 additions & 0 deletions .agents/wisdom/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@
- 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 = rate-limited

When the CodeRabbit status check shows SUCCESS but no review summary or inline
comments are posted (even minutes after triggering), and the trigger comment
has no reply, CodeRabbit was rate-limited on the free tier (35-min cooldown).
The status check lies — it shows SUCCESS even when no review ran.

**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 both are empty, the review was rate-limited — document and move on
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

## 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/
videos/
.playwright-cli/
.playwright-mcp/
.worktrees/
9 changes: 5 additions & 4 deletions examples/interactive-decision-demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,17 @@ 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.
Admin's `cars.delete`, adjusting the UTC business-hours window for both.
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.

## Stack

Expand Down
4 changes: 2 additions & 2 deletions examples/interactive-decision-demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ <h3 class="policy-block-title">Deny overrides</h3>
</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>
<p class="schedule-note">Configure the weekday work-hours schedule. Affects <code>reports.read</code> (Super Admin) and <code>cars.delete</code> (Admin).</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>
<span class="toggle-label">Enforce schedule on <code>reports.read</code> &amp; <code>cars.delete</code></span>
</label>
<div class="schedule-editor">
<div class="hour-input-group">
Expand Down
79 changes: 65 additions & 14 deletions examples/interactive-decision-demo/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,9 @@ const adapter = new MemoryAdapter({
{
name: "admin",
permissions: [
{ permission: "cars.*", effect: "grant" },
{ permission: "cars.read", effect: "grant" },
{ permission: "cars.create", effect: "grant" },
{ permission: "cars.update", effect: "grant" },
],
},
{
Expand Down Expand Up @@ -205,13 +207,19 @@ function applyDefaultPolicyFacts(): void {
effect: "grant",
schedule: makeWeekSchedule(9, 0, 17, 0),
});
policySource.addFact("admin", {
permission: "cars.delete",
effect: "grant",
schedule: makeWeekSchedule(9, 0, 17, 0),
});
}

function restorePolicyFactsFromSaved(saved: SavedState): void {
// Wipe dynamic facts for the two principals we manage.
// Wipe dynamic facts for the three principals we manage.
policySource.removeAllFacts("support", "cars.delete");
policySource.removeAllFacts("support", "cars.update");
policySource.removeAllFacts("super-admin", "reports.read");
policySource.removeAllFacts("admin", "cars.delete");

if (saved.deleteDeny) {
policySource.addFact("support", { permission: "cars.delete", effect: "deny" });
Expand All @@ -228,11 +236,23 @@ function restorePolicyFactsFromSaved(saved: SavedState): void {
saved.scheduleEndH, saved.scheduleEndM,
),
});
policySource.addFact("admin", {
permission: "cars.delete",
effect: "grant",
schedule: makeWeekSchedule(
saved.scheduleStartH, saved.scheduleStartM,
saved.scheduleEndH, saved.scheduleEndM,
),
});
} else {
policySource.addFact("super-admin", {
permission: "reports.read",
effect: "grant",
});
policySource.addFact("admin", {
permission: "cars.delete",
effect: "grant",
});
}
}

Expand Down Expand Up @@ -726,39 +746,70 @@ function updateClockDisplay(): void {
}

/**
* Replace the sole reports.read fact with one matching the current controls.
* Because reports.read exists ONLY in the policy source (not in the role),
* the editor is the single source of truth.
* Replace schedule-controlled facts (reports.read for super-admin, cars.delete for admin)
* with ones matching the current controls, then display both results.
*/
async function evaluateSchedule(): Promise<void> {
policySource.removeAllFacts("super-admin", "reports.read");
policySource.removeAllFacts("admin", "cars.delete");

if (scheduleEnabled) {
policySource.addFact("super-admin", {
permission: "reports.read",
effect: "grant",
schedule: makeWeekSchedule(scheduleStartH, scheduleStartM, scheduleEndH, scheduleEndM),
});
policySource.addFact("admin", {
permission: "cars.delete",
effect: "grant",
schedule: makeWeekSchedule(scheduleStartH, scheduleStartM, scheduleEndH, scheduleEndM),
});
} else {
policySource.addFact("super-admin", {
permission: "reports.read",
effect: "grant",
});
policySource.addFact("admin", {
permission: "cars.delete",
effect: "grant",
});
}

const evalSA = getEvaluator("super-admin");
const evalAdmin = getEvaluator("admin");
try {
const result = await evalSA.decide("reports.read", { at: clockTime });
const [saResult, adminResult] = await Promise.all([
evalSA.decide("reports.read", { at: clockTime }),
evalAdmin.decide("cars.delete", { at: clockTime }),
]);

const el = byId("schedule-result");
const isAllow = result.decision === "allow";
el.className = `schedule-result ${isAllow ? "allow" : "deny"}`;
el.textContent = isAllow
? scheduleEnabled
? "✓ reports.read allowed (inside schedule)"
: "✓ reports.read allowed (no schedule restriction)"
: `✗ reports.read denied (${result.reason ?? "unknown"})`;
const saOk = saResult.decision === "allow";
const adminOk = adminResult.decision === "allow";

el.innerHTML = `<div class="${saOk ? "allow" : "deny"}">${
saOk
? scheduleEnabled
? "✓ reports.read (Super Admin) — inside schedule"
: "✓ reports.read (Super Admin) — no restriction"
: `✗ reports.read (Super Admin) — ${saResult.reason ?? "unknown"}`
}</div><div class="${adminOk ? "allow" : "deny"}">${
adminOk
? scheduleEnabled
? "✓ cars.delete (Admin) — inside schedule"
: "✓ cars.delete (Admin) — no restriction"
: `✗ cars.delete (Admin) — ${adminResult.reason ?? "unknown"}`
}</div>`;
el.className = "schedule-result";

// Refresh the cars table and permissions sidebar so they reflect
// the updated schedule state (e.g., Admin's Delete buttons).
await Promise.all([
renderTable(),
renderPermissions(),
]);
} catch {
byId("schedule-result").textContent = "Error evaluating schedule";
byId("schedule-result").innerHTML = "<div>Error evaluating schedule</div>";
}
}

Expand Down
6 changes: 3 additions & 3 deletions examples/interactive-decision-demo/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,9 @@ body {
.clock-btn { padding: 0.15rem 0.35rem; font-family: var(--font-body); font-size: 0.65rem; font-weight: 500; border: 1px solid var(--paper-border); border-radius: 3px; background: var(--paper); color: var(--ink-muted); cursor: pointer; }
.clock-btn:hover { background: var(--paper-hover); color: var(--ink); }
.clock-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.schedule-result { margin-top: var(--space-sm); font-size: 0.75rem; font-weight: 500; }
.schedule-result.allow { color: var(--allow); }
.schedule-result.deny { color: var(--deny); }
.schedule-result { margin-top: var(--space-sm); font-size: 0.75rem; font-weight: 500; display: flex; flex-direction: column; gap: 0.2rem; }
.schedule-result .allow { color: var(--allow); }
.schedule-result .deny { color: var(--deny); }

.locked-notice { display: flex; gap: var(--space-md); align-items: flex-start; padding: var(--space-md); background: var(--paper-raised); border-radius: 4px; }
.locked-icon { font-size: 1rem; line-height: 1.4; }
Expand Down
Loading