Skip to content

fix: say why a closed assignment's history is refused - #72

Merged
rabesss merged 2 commits into
mainfrom
fix/closed-assignment-history
Oct 9, 2026
Merged

rabesss merged 2 commits into
mainfrom
fix/closed-assignment-history

Conversation

@rabesss

@rabesss rabesss commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

What

Brightspace refuses a learner's own submission history (mysubmissions, HTTP 403) once an assignment's end date has passed and its availability restricts access after that date. student assignment-history printed only "Permission denied (HTTP 403)." for every such assignment, which reads like a broken session or a wrong role.

On that 403, the CLI now reads the assignment folder once. If its end date has passed and EndDateAvailabilityType is 0 (access restricted after the end date), the error says when the assignment closed (in UTC) and to check released grades with lighthouse grades COURSE_ID:

Brightspace refused this (HTTP 403): the assignment closed on 2026-03-05 18:29 UTC and restricts access after that, so its submissions and feedback cannot be read here. Check released grades with: lighthouse grades 12

Everything else keeps the original message: type 1 (only submission restricted), 2 (hidden), a missing or non-integer type, a future, naive, date-only, oversized or out-of-range end date, an unreadable folder, any other status, and the instructor submissions read (which reads nothing more).

Docs

  • README: one sentence on the refusal and where to look instead.
  • .secrets.baseline: the audited README entry moved from line 340 to 343. Nothing else changed.

Testing

  • A parametrized CLI test covers 24 cases: closed (Z and a +05:30 offset normalised to UTC), every non-closed availability above (including year 0001 and 9999 dates that overflow in UTC), a non-dict folder or availability, a folder read that fails with 403 or a network error, an HTTPError with no response, 404, and NetworkError. It checks the exact JSON error, which reads were made, and that no secret sentinel (in the folder name, the date or an exception) reaches the output. Two more tests pin the message on stderr without --json and the instructor submissions 403. The new tests fail without the fix.
  • Live, read-only, against a production Brightspace learner account with this branch: a closed assignment gives the new message; an open assignment's history still returns normally; grades lists the course's items.
  • Full local gate passes: secrets, ruff, mypy, import-linter, deptry, xenon, and pytest (2217 passed).

Brightspace answers a learner's `mysubmissions` with 403 once an
assignment's end date has passed and access is restricted after it, so
`student assignment-history` printed only "Permission denied (HTTP 403)."
for every closed assignment, as if the session or role were wrong.

On that 403 the CLI now reads the folder once. If its end date has
passed and EndDateAvailabilityType is 0 (access restricted), it says when
the assignment closed, in UTC, and to check released grades with
`lighthouse grades COURSE_ID`. Any other availability, an unreadable
folder or date, any other status and the instructor `submissions` read
keep the original message. The README notes this; its audited
.secrets.baseline entry moved three lines.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@factory-droid

factory-droid Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Droid finished @rabesss's task —— View job


The change is well-scoped and correct: on a 403 the fallback reads the folder once, guards every malformed-availability case (wrong types, naive/future/oversized dates, unreadable folder), and re-raises the original error unchanged otherwise. The new error message is built only from a parsed datetime and a validated int course ID, so no server-controlled or secret-bearing text reaches output, and the --json contract is preserved. Tests cover 22 parametrized cases plus the instructor path; no issues found.

0 inline comments posted

@greptile-apps

greptile-apps Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no actionable issues were found.

Summary

The CLI now explains a learner's submission-history refusal when the assignment has closed and restricts access afterward.

  • Reads the assignment folder only after a learner-history HTTP 403.
  • Shows the closing time in UTC and points learners to released grades.
  • Keeps the original error for other cases and leaves instructor reads unchanged.
  • Adds tests for the new message and fallback cases, plus a README note.

Reviews (1) · Last reviewed commit: "fix: say why a closed assignment's histo..." · Reviewed by Greptile

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes

  • On a 403 from a learner's mysubmissions read, AssessmentAPI.submissions (mine=True) re-reads the dropbox folder once and, when the end date has passed with EndDateAvailabilityType 0, raises AssignmentClosedError naming the close time in UTC and pointing at lighthouse grades COURSE_ID.
  • _closed_at fails closed to the original 403 on every ambiguous shape: non-int (incl. bool/string/null) availability type, missing or non-dict Availability, naive, date-only, future, unparseable, or oversized EndDate, and any folder-read failure.
  • _run surfaces AssignmentClosedError via its fixed local message instead of format_user_error; the instructor submissions path (mine=False) is an early direct return, and a test pins that its 403 reads nothing more.
  • Output stays secret-safe: the message contains only fixed text, a parsed-and-reformatted datetime, and the validated course id — no raw server string is echoed, matching the existing _safe_http_error discipline, and a NEVER_PRINT sentinel assertion pins it.
  • Docs/baseline: one README sentence; .secrets.baseline diffs are exactly the line shifts from those edits plus generated_at.
  • Tests: 22-case parametrized matrix covering the closed case (both Z and offset timestamps) and every fail-closed branch, plus the instructor pin — verified locally (72 passed in tests/test_assessment_workflows.py), and scripts/check_secrets.py runs clean against the regenerated baseline.

One narrow edge noted and accepted: if the session expires between the refused history read and the folder read, _closed_at's except Exception swallows the SessionExpiredError and the user sees the original 403 rather than the session-expiry hint. This requires the session to die between two back-to-back requests while the first still returned a cookie-bearing 403 — not worth the extra branch.

Pullfrog  | View workflow run | Using glm-5.3 | 𝕏

"""When this folder closed, if its end date has passed with access restricted after it."""
try:
folder = self.read("assignment", folder_id)
except Exception:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: Broad except Exception hides non-transport bugs during the probe

The fallback intent (any unreadable folder keeps the original 403 message) is sound, but this also swallows programming errors in the read path - e.g. a future refactor regression raising TypeError/AttributeError would silently surface as the generic "Permission denied (HTTP 403)." instead of failing loudly in tests. Consider narrowing to the failure types this probe can actually hit, e.g. except (requests.RequestException, NetworkError, SessionExpiredError):.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread lighthouse_cli/assessment_api.py Outdated
availability = folder.get("Availability") if isinstance(folder, dict) else None
if not isinstance(availability, dict):
return None
# AvailabilityDateType 0 restricts access after the end date (1 restricts only submission).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: Comment names AvailabilityDateType, but the field read on the next line is EndDateAvailabilityType

The identifier in this comment matches neither the payload key nor the code, which can mislead the next reader about which value drives the "restricted after end date" behavior.

Suggested change
# AvailabilityDateType 0 restricts access after the end date (1 restricts only submission).
# EndDateAvailabilityType 0 restricts access after the end date (1 restricts only submission).

Reply with @kilocode-bot fix it to have Kilo Code address this issue.



class AssignmentClosedError(ValueError):
"""Brightspace refused a learner's own submissions after the folder closed; fixed local message only."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: Usable message depends on every caller special-casing this type

Only _run in assessment_commands.py currently maps AssignmentClosedError to str(exc); any future caller that routes it through format_user_error instead degrades to "Permission denied (HTTP 403)." (the status pattern in the message still matches), silently losing the closure explanation and the lighthouse grades hint. Safe today since submissions(mine=True) has a single caller, but worth a note so the coupling to _run is discoverable.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread tests/test_assessment_workflows.py Outdated
result = CliRunner().invoke(cli, ["student", "assignment-history", "12", "34", "--json"])
assert result.exit_code == 1
assert json.loads(result.stdout) == {"course_id": 12, "data": None,
"error": CLOSED if closed else format_user_error(history)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: Expected error is self-referential for the 20 non-closed cases

format_user_error(history) appears on both sides of the comparison, so these assertions stay green even if the formatter regresses (e.g. starts leaking transport text or drops the status category). Pinning the literal outputs - like the instructor test below does with "Permission denied (HTTP 403)." - would keep these cases guarding real CLI output.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: 2 Issues Found | Recommendation: Merge — suggestions only, none blocking

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 2
Issue Details (click to expand)

SUGGESTION

File Line Issue
lighthouse_cli/assessment_api.py 197 Broad except Exception around the folder probe also hides non-transport bugs (e.g. TypeError regressions) as the generic 403 message; consider narrowing to (requests.RequestException, NetworkError, SessionExpiredError).
lighthouse_cli/assessment_api.py 33 The helpful closed-assignment message only appears because _run special-cases AssignmentClosedError; any future caller using format_user_error silently degrades to "Permission denied (HTTP 403)."
Files Reviewed (3 files)
  • .secrets.baseline - 0 issues (line-number drift and timestamp only)
  • lighthouse_cli/assessment_api.py - 2 issues (both carried from the previous review, re-verified on current head)
  • tests/test_assessment_workflows.py - 0 issues
Notes
  • Incremental changes verified clean: moving the tz/freshness check and astimezone inside the try now catches the OverflowError that out-of-range dates (e.g. 0001-01-01T00:00:00+01:00, 9999-12-31T23:59:00-01:00) previously raised past _closed_at; the new min-date/max-date cases cover it. end.replace("Z", "+00:00") is required for the pinned requires-python = ">=3.10" (3.10's fromisoformat rejects Z), so it is not redundant.
  • Test hardening is sound: expected errors are now pinned literals, the parametrize has 24 cases matching 24 ids, the reads assertion requires exactly N calls, the new stderr test is valid under pinned click>=8.2, and the NEVER_PRINT sentinel now also sits in the folder Name.
  • Secret hygiene and the --json contract hold on current code: the message embeds only a UTC datetime and the validated course id, raise ... from None keeps response text out of tracebacks, and errors flow through fail() (stderr + JSON envelope, exit 1).

Fix these issues in Kilo Cloud

Previous Review Summary (commit 3f8b711)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 3f8b711)

Status: 4 Issues Found | Recommendation: Merge — suggestions only, none blocking

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 4
Issue Details (click to expand)

SUGGESTION

File Line Issue
lighthouse_cli/assessment_api.py 197 Broad except Exception around the folder probe also hides non-transport bugs (e.g. TypeError regressions) as the generic 403 message; consider narrowing to (requests.RequestException, NetworkError, SessionExpiredError).
lighthouse_cli/assessment_api.py 202 Comment names AvailabilityDateType but the field read is EndDateAvailabilityType; misnomer can mislead readers.
lighthouse_cli/assessment_api.py 33 The helpful closed-assignment message only appears because _run special-cases AssignmentClosedError; any future caller using format_user_error silently degrades to "Permission denied (HTTP 403)."
tests/test_assessment_workflows.py 222 Expected error for the 20 non-closed cases is self-referential (format_user_error(history) on both sides), so formatter regressions stay green; pin literal outputs like the instructor test does.
Files Reviewed (5 files)
  • .secrets.baseline - 0 issues (line-number drift and timestamp only)
  • README.md - 0 issues
  • lighthouse_cli/assessment_api.py - 3 issues
  • lighthouse_cli/assessment_commands.py - 0 issues
  • tests/test_assessment_workflows.py - 1 issue
Notes
  • Secret hygiene is sound: the new message embeds only a UTC datetime and the validated course id, raise ... from None prevents response text from entering tracebacks, and the tests assert a NEVER_PRINT sentinel never reaches output.
  • The --json contract holds: errors go through fail(), which echoes the human message to stderr, emits the JSON envelope to stdout, and exits 1.
  • Date handling is defensively validated (bool-typed restriction rejected, naive/future/oversized/non-ISO end dates all fall back to the original message).

Fix these issues in Kilo Cloud


Reviewed by glm-5.3-flash · Input: 76.1K · Output: 12.9K · Cached: 349K

Review guidance: REVIEW.md from base branch main

An aware end date at the edge of the datetime range, such as
"0001-01-01T00:00:00+01:00" with access restricted, parsed and passed the
checks, then overflowed when converted to UTC, so the CLI printed
"Command failed." instead of the original "Permission denied (HTTP 403)."
(GPT-6.1 Sol, Grok 4.7). The comparison and the conversion now fall back
like any unreadable date; min and max dates are pinned.

Tests now pin each fallback's literal message instead of recomputing it
with format_user_error (Kilo Code Review), give the closed fixtures a
secret-bearing folder name (Muse Spark 1.3), and pin the closed message
on stderr with empty stdout without --json (MiMo v2.6 Flash). The code
comment names EndDateAvailabilityType (Kilo Code Review). The test
file's audited .secrets.baseline entry is back on its original line.
@factory-droid

factory-droid Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Droid encountered an error —— View job

Droid could not run because a usage limit was reached: You've reached your monthly Droid Core usage limit (resets in 4 days).
Reload Extra Usage credits or wait for your limits to reset.

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes

Re-reviewed the delta since the prior review (3f8b711) — one commit hardening the closed-assignment fallback's datetime handling, plus test strengthening.

  • Fixed a real OverflowError escape in _closed_at: the timezone check and astimezone(timezone.utc) conversion now run inside the try with the catch widened to (ValueError, OverflowError), so end dates at datetime's bounds (0001-01-01T00:00:00+01:00, 9999-12-31T23:59:00-01:00 parse fine but overflow once shifted to UTC) fail closed to the original 403 instead of raising out of the 403 handler into a generic message.
  • Corrected the availability comment from AvailabilityDateType to EndDateAvailabilityType, matching the field actually read.
  • Strengthened the test matrix: added min-date/max-date cases pinning the overflow fix, switched every case to asserting the exact expected error string (404 and network messages included, no longer derived from format_user_error), embedded a NEVER_PRINT sentinel in the folder Name to pin that it never reaches output, and added a test pinning the closed message to stderr with empty stdout on the non---json path — matching fail()'s err=True contract.
  • Reverted the test-only format_user_error import, restoring the import block (and the .secrets.baseline test-file entry) to the base shape.

Verified locally: tests/test_assessment_workflows.py 75 passed, ruff check clean on the changed files, scripts/check_secrets.py clean against the regenerated baseline.

Pullfrog  | View workflow run | Using glm-5.3 | 𝕏

@rabesss
rabesss merged commit bda9d40 into main Oct 9, 2026
8 of 9 checks passed
@rabesss
rabesss deleted the fix/closed-assignment-history branch October 9, 2026 17:45
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