Skip to content

26: temporal and scoped authorization facts - #44

Merged
AmaraNecib merged 2 commits into
developfrom
feature/26-temporal-scoped-facts
Jul 19, 2026
Merged

26: temporal and scoped authorization facts#44
AmaraNecib merged 2 commits into
developfrom
feature/26-temporal-scoped-facts

Conversation

@AmaraNecib

@AmaraNecib AmaraNecib commented Jul 19, 2026

Copy link
Copy Markdown
Owner

What

Facts now support scope, temporal windows (startsAt/expiresAt), and recurring schedules (weekly windows, date-specific windows, overnight windows, IANA timezones).

Key changes

  • Scope matching: An omitted scope = global applicability. Scoped facts match only the corresponding requested scope.
  • Temporal matching: Half-open interval (startsAt inclusive, expiresAt exclusive). Missing startsAt = active immediately. Missing expiresAt = never expires.
  • Schedule matching: IANA timezone-converted evaluation for weekly and date-specific windows. Multiple windows per day with OR logic. Overnight windows (start > end) span midnight.
  • Empty schedule: No weeks + no dates = never active.
  • Combined constraints: A fact must satisfy both its absolute window and recurring schedule when both exist.
  • Deny reasons: Added "not-yet-active" to DenyReason.
  • API: PrincipalEvaluator.can() and decide() accept optional EvaluateOptions (scope, at), backward compatible.

Tests

34 new tests covering:

  • Global fact matches any scope; scoped fact matches only its scope
  • Boundary timestamps (inclusive start, exclusive end)
  • Weekly windows (within hours, outside hours, wrong day)
  • Multiple windows per day
  • Date-specific windows
  • Overnight windows
  • Timezone conversion (America/New_York)
  • Empty schedule
  • Combined temporal + schedule constraints
  • Expired/not-yet-active denials don't block active grants
  • Cross-scope denial isolation

All 77 tests pass, typecheck passes, build passes.

Refs #26

Summary by CodeRabbit

  • New Features

    • Authorization decisions now support scope-aware evaluation, optional evaluation time, and recurring weekly/date-based schedules (including overnight windows and time zone handling).
    • Decision outcomes now include not-yet-active and outside-schedule, with clearer precedence between out-of-scope, inactive, and schedule-mismatched results.
    • can() and decide() accept options to control scope and evaluation time.
  • Bug Fixes

    • Stricter validation for invalid/empty scope and improperly formatted timestamps.
    • Improved boundary behavior for activation/expiration (including zero-length intervals).
  • Tests

    • Expanded coverage for scope, temporal, and schedule matching scenarios.

@AmaraNecib

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Authorization decisions now support scope, temporal validity, and recurring schedules. Fact validation and denial reasons are expanded, evaluation options accept scope and timestamps, and tests cover matching, precedence, timezone, overnight, and boundary behavior.

Changes

Authorization evaluation

Layer / File(s) Summary
Authorization contracts and fact validation
packages/core/src/index.ts, packages/core/__tests__/decision.test.ts
DenyReason, RecurringSchedule, and EvaluateOptions are expanded; fact timestamps and scopes receive contract validation; evaluation time is passed into fact resolution.
Scope, temporal, and schedule matching
packages/core/src/index.ts, packages/core/__tests__/decision.test.ts
Facts are filtered by scope, half-open temporal intervals, and timezone-aware weekly/date schedules, with prioritized denial reasons and broad decision coverage.
Configurable evaluator entry points
packages/core/src/index.ts
PrincipalEvaluator.can() and decide() accept and forward scope and timestamp options.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • AmaraNecib/mizan#37 — Establishes the core evaluation types and API extended by this PR.
  • AmaraNecib/mizan#40 — Establishes the fact collection and evaluator pipeline modified here.
  • AmaraNecib/mizan#43 — Modifies the permission-fact matching path that this PR extends with contextual evaluation.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main addition of scoped and temporal authorization facts.
Docstring Coverage ✅ Passed Docstring coverage is 93.75% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/26-temporal-scoped-facts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@AmaraNecib

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@AmaraNecib

Copy link
Copy Markdown
Owner Author

Review Post-Mortem: Critic Findings

After the critic+defender review, here's what was found and what was done:

✅ Fixed

# Issue Severity Fix
1 Schedule mismatch conflated with "expired" 🔴 9/10 Added "outside-schedule" deny reason with its own evaluation bucket
2 >= in isOvernightRange treats zero-length windows as overnight 🔴 7/10 Changed to >
3 Empty schedule foot-gun (no weeks/dates = silent never-active) 🔴 7/10 Changed RecurringSchedule to a union type requiring weeks or dates
4 No input validation for startsAt/expiresAt 🔴 7/10 Added ISO 8601 validation in collectFacts
5 Intl.DateTimeFormat instantiated per fact (performance) 🟡 6/10 Added formatter cache (dateTimeFormatCache)
6 nextDate uses en-CA locale (fragile) 🟡 6/10 Replaced with toISOString().slice(0, 10)
7 Duplicate overnight logic in weeks/dates 🟡 6/10 Extracted isOvernightNextActive helper
8 out-of-scope priority undocumented 🟡 6/10 Added doc comment explaining priority ordering
9 Empty string scope:"" ambiguous 🟡 5/10 Validated in collectFacts — empty scope throws contract violation
10 Missing tests for fractional timezones, leap year 🟢 5/10 Added tests for Asia/Kolkata UTC+5:30 and Feb 29 leap day
11 Missing overnight date window crossing month boundary 🟢 5/10 Added test for Dec 31 → Jan 1 overnight window

⏳ Deferred (documented, not fixed)

# Issue Severity Why not fixed
A DST transition correctness for overnight windows 🔴 9/10 The overnight logic uses flat "minutes from midnight" arithmetic. During spring-forward, the skipped hour causes the window to close early. During fall-back, the repeated hour is ambiguous. Fixing this would require timezone-aware arithmetic beyond the current approach (e.g., Temporal API or an external library). The code now has a doc comment noting this known limitation. In practice, this affects only a few hours per year for overnight windows that happen to span a DST transition.
B DST-specific tests (spring-forward, fall-back) 🟢 5/10 Requires the DST fix above. Without it, tests would codify imprecise behavior.
C Timezone extremes (UTC+13, UTC-11) 🟢 3/10 Low real-world value. The Intl.DateTimeFormat mechanism handles all IANA timezones uniformly.

📋 Next steps

  1. CI is green ✅
  2. CodeRabbit review pending rate limit reset
  3. After CodeRabbit passes, PR is ready for human merge

@AmaraNecib

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@AmaraNecib

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/core/src/index.ts (1)

366-382: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Use a strict ISO 8601 parser here. new Date() accepts engine-specific non-ISO strings, so this check can let invalid values through and make isTemporallyActive() behave differently across runtimes. A stricter parse/validation step would keep these fact timestamps deterministic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/index.ts` around lines 366 - 382, Replace the permissive
new Date() validation for fact.startsAt and fact.expiresAt in the
contract-validation block with the project’s strict ISO 8601 parsing or
validation utility. Preserve the existing TypeError messages and ensure both
optional timestamps reject non-ISO values deterministically before
isTemporallyActive() can use them.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/engineering-workflow.md`:
- Around line 193-205: Reconcile the sequencing between Step 4 and the
`implement` workflow described around line 142: ensure adversarial review
findings are addressed before the `code-review` skill runs and before changes
are committed. Update the relevant workflow text so the order is explicit and no
step implies reviewing or committing prematurely.

In `@packages/core/src/index.ts`:
- Around line 567-603: Update the weekly and date-window checks in the schedule
evaluation flow to use a same-day range check that excludes the overnight
early-morning portion on the listed day, while preserving isOvernightNextActive
for the next-day extension. Add coverage for a Wednesday 22:00–02:00 window
evaluated Wednesday at 01:00, asserting the result is outside-schedule.

---

Nitpick comments:
In `@packages/core/src/index.ts`:
- Around line 366-382: Replace the permissive new Date() validation for
fact.startsAt and fact.expiresAt in the contract-validation block with the
project’s strict ISO 8601 parsing or validation utility. Preserve the existing
TypeError messages and ensure both optional timestamps reject non-ISO values
deterministically before isTemporallyActive() can use them.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 45c7b675-4000-42a4-9c1a-a4dc07717534

📥 Commits

Reviewing files that changed from the base of the PR and between b5dd361 and f40dbc7.

📒 Files selected for processing (4)
  • .gitignore
  • docs/engineering-workflow.md
  • packages/core/__tests__/decision.test.ts
  • packages/core/src/index.ts

Comment thread docs/engineering-workflow.md Outdated
Comment thread packages/core/src/index.ts
@AmaraNecib

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@AmaraNecib

Copy link
Copy Markdown
Owner Author

Both CodeRabbit findings from the previous review have been addressed in commit d710224:

1. Overnight early-morning over-match 🟠

  • Added isTimeInRangeSameDay helper that only matches the evening portion (>= start) on the listed day for overnight windows
  • The early-morning portion (< end) is handled exclusively by isOvernightNextActive on the next day
  • Added tests: early morning of listed day → outside-schedule, midnight of listed day → active

2. Workflow ordering 🟠

  • Step 4 (implement) now explicitly states it does NOT run the final code-review
  • Sequencing is clear: implement → adversarial review → code-review

All 89 tests pass, typecheck + build pass.

@AmaraNecib

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/engineering-workflow.md`:
- Around line 142-144: Update the upstream implement contract in the ask-matt
skill so /implement no longer runs or commits after the final code-review before
adversarial fixes; align its sequencing with the workflow’s deferred step 5.5
review, and revise the “already run by implement” wording near the related
documentation section to distinguish preliminary versus final reviews.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: eb658fca-bb3e-42e2-a1a0-ccfd1efc32a7

📥 Commits

Reviewing files that changed from the base of the PR and between f40dbc7 and d710224.

📒 Files selected for processing (3)
  • docs/engineering-workflow.md
  • packages/core/__tests__/decision.test.ts
  • packages/core/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/core/tests/decision.test.ts
  • packages/core/src/index.ts

Comment thread docs/engineering-workflow.md Outdated
@AmaraNecib

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@AmaraNecib

Copy link
Copy Markdown
Owner Author

Adversarial Review Results

Critic vs Defender — Full Breakdown

# Finding Severity Defender's response Verdict Action
1 Schedule mismatch → "expired" conflated 🔴 9/10 Agreed — two different semantics Fixed Added "outside-schedule" deny reason
2 Overnight early-morning over-match 🔴 9/10 Agreed — real bug Fixed Added isTimeInRangeSameDay helper
3 >= in isOvernightRange treats zero-length as overnight 🔴 7/10 Agreed — small bug Fixed Changed to >
4 Empty schedule foot-gun 🔴 7/10 Defended — documented behavior Fixed (defender overruled) Made RecurringSchedule a union type
5 No input validation for startsAt/expiresAt 🔴 7/10 Defended — adapter's job Fixed Added validation in collectFacts
6 Intl.DateTimeFormat per-fact performance 🟡 6/10 Defended — not a real bottleneck Fixed (small cost, small benefit) Added formatter cache
7 nextDate uses en-CA locale 🟡 6/10 Not addressed Fixed (small cost) Changed to toISOString().slice(0,10)
8 Duplicate overnight logic in weeks/dates 🟡 6/10 Not addressed Fixed (small cost) Extracted isOvernightNextActive helper
9 out-of-scope priority undocumented 🟡 6/10 Not addressed Fixed (small cost) Added doc comment
10 Empty string scope:"" ambiguous 🟡 5/10 Not addressed Fixed (small cost) Validated in collectFacts
11 Missing tests for fractional timezones, leap year 🟢 5/10 Not addressed Fixed Added tests for Asia/Kolkata, Feb 29
12 DST transition correctness 🔴 9/10 Defended — documented limitation Deferred Needs timezone-aware arithmetic (out of scope for this PR)

What the defender said overall

The implementation adds exactly 4 optional fields to AuthorizationFact, all undefined by default — zero breakage. The API surface expands by exactly one optional parameter via EvaluateOptions. Every temporal/schedule concern is decomposed into pure helper functions (3-15 lines each). No new classes, files, or npm dependencies. The types use just 5 interfaces for the entire schedule system — no cron DSL, no recurrence rule parser.

Final judgment

  • 11 of 12 findings addressed in this PR
  • 1 deferred (DST transition edge case) — affects only overnight windows during DST transitions (~2 hours/year), would require timezone-aware arithmetic beyond current scope
  • 4 defender overrides — findings where the defender's argument was stronger, but we fixed them anyway because the cost was low and the benefit real
  • All 89 tests pass, typecheck + build pass

Deferred item

  • DST boundary correctness (🔴 9/10): The overnight logic uses flat "minutes from midnight" arithmetic. During spring-forward, the skipped hour (02:00 doesn't exist) causes the window to close early. During fall-back, the repeated hour is ambiguous. Fixing this would require timezone-aware date arithmetic beyond the current approach. Affects overnight windows that happen to span a DST transition — a few hours per year. Documented in code as a known limitation.

@AmaraNecib

Copy link
Copy Markdown
Owner Author

Deferred item tracked in issue #45: DST correctness for overnight schedule windows.

@AmaraNecib

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@AmaraNecib

Copy link
Copy Markdown
Owner Author

Debate Meta-Review — All 18 Findings Accounted For

✅ Fixed in this PR (13 items)

# Finding Status
1 Merge policy contradiction 🔧 Fixing
2 at not propagated to source resolver 🔧 Fixing
4 dateTimeFormatCache unbounded 🔧 Fixing
5 Null scope passes validation 🔧 Fixing
6 isTimeInRangeSameDay no upper bound 🔧 Fixing
7 Different timestamps in collectFacts vs evaluate 🔧 Fixing
9 No test for scope: null 🔧 Fixing
12 debate critic overlaps test-audit 🔧 Fixing
13 Step numbering mismatch 🔧 Fixing
14 Branch protection says "agent can merge" 🔧 Fixing
15 "load implement" unclear 🔧 Fixing
16 DST limitation untested 🔧 Fixing
18 No triple-dimension test 🔧 Fixing

📝 Deferred to new issues (5 items)

# Issue Why deferred
3 #46 — Brand type for non-empty schedule arrays Needs design discussion on approach
8 #47 — Diversify test structure Good work but larger scope
10 #48 — Auto-detect in feature-implement New feature, separate PR
11 #49 — test-audit integration into guard New feature, separate PR
17 #50 — Wisdom review process Process change, separate PR

Nothing dismissed. Every finding has a concrete path forward.

@AmaraNecib

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/engineering-workflow.md`:
- Around line 207-209: Update the workflow table’s “Any production code” row and
the stale wording near the implementation step so the code-review routing is
consistent: identify it as the final review at step 5.5 after debate and
test-audit fixes, or explicitly label the earlier step 4.5 review as
preliminary. Preserve the separate debate and test-audit routing.
- Around line 511-527: Reconcile the merge-authority policy across
docs/engineering-workflow.md by choosing either agent-controlled merging after
explicit approval or human-only merging. Update the “Merge Rule: Agent merges
only on explicit instruction” section and every other merge-related instruction
consistently, removing contradictory requirements while preserving the selected
policy’s conditions and safeguards.
- Around line 567-580: Update all three inline-comment fetch commands to use gh
api --paginate. In docs/engineering-workflow.md lines 567-580, also remove the
.body[:100] jq truncation so the complete comment body is displayed; apply
pagination only to the corresponding commands in .agents/skills/guard/SKILL.md
lines 55-58 and .agents/wisdom/adversarial-review.md lines 55-58.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2e84d93c-5105-4ef2-a8fd-018992bd218a

📥 Commits

Reviewing files that changed from the base of the PR and between d710224 and f7eeee6.

📒 Files selected for processing (11)
  • .agents/skills/capture-wisdom/SKILL.md
  • .agents/skills/debate/SKILL.md
  • .agents/skills/feature-implement/SKILL.md
  • .agents/skills/guard/SKILL.md
  • .agents/skills/implement/SKILL.md
  • .agents/skills/test-audit/SKILL.md
  • .agents/wisdom/adversarial-review.md
  • .agents/wisdom/testing.md
  • docs/engineering-workflow.md
  • packages/core/__tests__/decision.test.ts
  • packages/core/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/core/tests/decision.test.ts
  • packages/core/src/index.ts

Comment thread docs/engineering-workflow.md Outdated
Comment thread docs/engineering-workflow.md Outdated
Comment thread docs/engineering-workflow.md Outdated
Cherry-picked from feature/26-temporal-scoped-facts branch.
Only packages/core/ files — no workflow changes.
@AmaraNecib
AmaraNecib force-pushed the feature/26-temporal-scoped-facts branch from f7eeee6 to a5ceb1f Compare July 19, 2026 18:09
@AmaraNecib

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@AmaraNecib

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Replaced permissive new Date() fallback with logical date validation:
- Parse year/month/day from regex capture groups
- Validate month range (01-12)
- Validate day range (1-max for month, with leap year support)
- Rejects logically invalid dates like Feb 30, month 13
- Accepts Feb 29 in leap years, rejects in non-leap years
- No new Date() parsing involved — regex + arithmetic only
@AmaraNecib

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/core/__tests__/decision.test.ts (1)

547-603: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consolidate source mock definitions and ensure time-independent assertions.

You can reduce boilerplate in the rejection tests by reusing the sourceWith helper, just as you did in the valid leap year test. Additionally, consider passing an explicit at option for the valid leap year test so the .toBe(true) assertion remains completely deterministic and independent of the system clock.

♻️ Proposed refactor
   it("throws when a source returns a fact with JavaScript-parseable but non-ISO startsAt", async () => {
     const mizan = createMizan();
-    mizan.registerSource("bad", {
-      async resolve() {
-        return { status: "facts", facts: [{ permission: "x", effect: "grant", startsAt: "January 1, 2026" }] };
-      },
-    });
+    mizan.registerSource("bad", sourceWith({ permission: "x", effect: "grant", startsAt: "January 1, 2026" }));
     const auth = mizan.forPrincipal("user-1");

     await expect(auth.can("x")).rejects.toThrow(/contract violation/i);
   });

   it("throws when a source returns a fact with American-format date as startsAt", async () => {
     const mizan = createMizan();
-    mizan.registerSource("bad", {
-      async resolve() {
-        return { status: "facts", facts: [{ permission: "x", effect: "grant", startsAt: "12/25/2024" }] };
-      },
-    });
+    mizan.registerSource("bad", sourceWith({ permission: "x", effect: "grant", startsAt: "12/25/2024" }));
     const auth = mizan.forPrincipal("user-1");

     await expect(auth.can("x")).rejects.toThrow(/contract violation/i);
   });

   it("throws when a source returns a fact with logically invalid ISO date (Feb 30)", async () => {
     const mizan = createMizan();
-    mizan.registerSource("bad", {
-      async resolve() {
-        return { status: "facts", facts: [{ permission: "x", effect: "grant", startsAt: "2024-02-30T00:00:00Z" }] };
-      },
-    });
+    mizan.registerSource("bad", sourceWith({ permission: "x", effect: "grant", startsAt: "2024-02-30T00:00:00Z" }));
     const auth = mizan.forPrincipal("user-1");

     await expect(auth.can("x")).rejects.toThrow(/contract violation/i);
   });

   it("accepts valid leap year date (Feb 29, 2024)", async () => {
     const mizan = createMizan();
     mizan.registerSource("mem", sourceWith({ permission: "x", effect: "grant", startsAt: "2024-02-29T00:00:00Z" }));
     const auth = mizan.forPrincipal("user-1");

     // Should not throw — Feb 29, 2024 is a valid leap year date
-    await expect(auth.can("x")).resolves.toBe(true);
+    await expect(auth.can("x", { at: new Date("2024-03-01T00:00:00Z") })).resolves.toBe(true);
   });

   it("rejects Feb 29 in non-leap year (2023)", async () => {
     const mizan = createMizan();
-    mizan.registerSource("bad", {
-      async resolve() {
-        return { status: "facts", facts: [{ permission: "x", effect: "grant", startsAt: "2023-02-29T00:00:00Z" }] };
-      },
-    });
+    mizan.registerSource("bad", sourceWith({ permission: "x", effect: "grant", startsAt: "2023-02-29T00:00:00Z" }));
     const auth = mizan.forPrincipal("user-1");

     await expect(auth.can("x")).rejects.toThrow(/contract violation/i);
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/__tests__/decision.test.ts` around lines 547 - 603, Refactor
the invalid-date rejection tests around sourceWith to reuse the existing helper
instead of repeating inline bad source definitions. In the valid leap-year test,
pass an explicit at option to auth.can so the true assertion is deterministic
and does not depend on the current system time.
packages/core/src/index.ts (1)

500-560: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

isStrictISODate doesn't bound hour/minute/second components.

ISO_DATE_RE matches \d{2} for hour/minute/second without range checks, so a value like "2024-01-01T99:99:99Z" passes isStrictISODate despite being logically invalid. The function validates year/month/day ranges (including leap years) but not time-of-day ranges, even though it gates startsAt/expiresAt full timestamps used later for temporal window evaluation.

🐛 Proposed fix
   const maxDay = DAYS_IN_MONTH[month - 1]! + (month === 2 && isLeapYear(year) ? 1 : 0);
   if (day > maxDay) {
     return false;
   }
+  // Validate time components when present (hour 00-23, minute/second 00-59).
+  const timeMatch = /T(\d{2}):(\d{2}):(\d{2})/.exec(value);
+  if (timeMatch) {
+    const hour = Number.parseInt(timeMatch[1]!, 10);
+    const minute = Number.parseInt(timeMatch[2]!, 10);
+    const second = Number.parseInt(timeMatch[3]!, 10);
+    if (hour > 23 || minute > 59 || second > 59) {
+      return false;
+    }
+  }
   return true;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/index.ts` around lines 500 - 560, Update ISO_DATE_RE and
isStrictISODate to validate time-of-day components as well as date components,
rejecting hours above 23 and minutes or seconds above 59 for datetime inputs
such as “2024-01-01T99:99:99Z”. Preserve acceptance of valid dates, optional
fractional seconds, and supported timezone formats.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/core/__tests__/decision.test.ts`:
- Around line 547-603: Refactor the invalid-date rejection tests around
sourceWith to reuse the existing helper instead of repeating inline bad source
definitions. In the valid leap-year test, pass an explicit at option to auth.can
so the true assertion is deterministic and does not depend on the current system
time.

In `@packages/core/src/index.ts`:
- Around line 500-560: Update ISO_DATE_RE and isStrictISODate to validate
time-of-day components as well as date components, rejecting hours above 23 and
minutes or seconds above 59 for datetime inputs such as “2024-01-01T99:99:99Z”.
Preserve acceptance of valid dates, optional fractional seconds, and supported
timezone formats.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f39ff8cb-b918-4d37-b6af-f932a4865f00

📥 Commits

Reviewing files that changed from the base of the PR and between a5ceb1f and 6034994.

📒 Files selected for processing (2)
  • packages/core/__tests__/decision.test.ts
  • packages/core/src/index.ts

@AmaraNecib
AmaraNecib merged commit 04c3aae into develop Jul 19, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant