Record the unported attachment thumbnails/click-to-view as a rewrite task - #121
Closed
corrin wants to merge 19 commits into
Closed
Record the unported attachment thumbnails/click-to-view as a rewrite task#121corrin wants to merge 19 commits into
corrin wants to merge 19 commits into
Conversation
…t-password surface Django's four standard validators, with the similarity attributes named explicitly because Staff has no username/email attribute for the defaults to find. _set_staff_password was already wired to enforce them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H4YMRsFzJQrHHzBkXHfYFC
The v1 repo now sits at ../docketworks_v1, so gen_v1_operations.py's V1_REPO and every live doc pointing at ../docketworks (which now resolves to this repo itself) move with it. initial_install.md clones the renamed GitHub repo. DB names stay docketworks_v2 - the database was not renamed. Historical records (cutover checklist, plan docs) untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0164MHjth7CvHoYQDEcZHahA
Verifies the current password (400 on mismatch, ADR 0038 transparent post-auth), routes the new value through _set_staff_password so the validators judge it and password_needs_reset clears. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PWTJcCbpQdzWLMVNg1QaLb
While the flag is set, CookieJWTAuth refuses every path outside the /me/ and /me/password/ allowlist with code "password_change_required" (error_id null, no AppError row — expected security outcome, ADR 0013). A frontend redirect the API does not back would leave every endpoint serving a session whose credential we have decided not to trust. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PWTJcCbpQdzWLMVNg1QaLb
…rfaces StaffCreateIn/StaffUpdateIn carry the "must change at next login" flag; an explicit flag outlives _set_staff_password's clear, so an admin can issue a known temporary password already flagged. UserProfile and StaffListItemOut expose the stored value — the route guard reads /me/, and the edit modal must render the real state or clear it by accident. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PWTJcCbpQdzWLMVNg1QaLb
…ptor Login response and /me both carry password_needs_reset; the login page and the authed layout route flagged sessions to /change-password (its own top-level route — nesting under _authed would loop that guard). The transport interceptor hard-navigates on the auth layer's typed 403 so a session flagged mid-flight gets walked to the exit too; the server gate stays the control, all of this is navigation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PWTJcCbpQdzWLMVNg1QaLb
The password-reset email gives application code its first Google call, and apps must never import from scripts — so the one credential builder moves to apps/core/gauth.py (scripts/gdocs re-imports it) and apps/core/gmail.py becomes the one application email sender: plain-text send as the instance's Workspace user via domain-wide delegation with the gmail.send scope, proven by the 2026-08-31 delegation probe. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PWTJcCbpQdzWLMVNg1QaLb
POST /password-reset/ answers a fixed 200 whether or not the address has an active account, and emails a uid+token link via the delegated Gmail sender. POST /password-reset/confirm/ exchanges the link for a new password through _set_staff_password (validators run, flag clears); refusals are declared 400 bodies because the envelope masks anonymous exception text and the validator's reason is the response. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PWTJcCbpQdzWLMVNg1QaLb
/forgot-password always lands on the same sent-confirmation copy — the server's fixed 200 must not be undone by a chattier client. The emailed link lands on /reset-password, whose uid/token search params normalise to the invalid-link state rather than crashing; a dead link surfaces as the server's 400 detail on submit, since the token is deliberately unverifiable without attempting the change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PWTJcCbpQdzWLMVNg1QaLb
The spec locks a flagged login to /change-password until a strong password lands, and walks the anonymous forgot/reset paths — the request step submits an account-less address (the fixed 200 sends nothing), so the E2E stack needs no Gmail configuration. The real delegated send is the integration gate's job (ADR 0050), addressed to the delegated subject itself so the probe stays in the instance's own inbox. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PWTJcCbpQdzWLMVNg1QaLb
…t falls Playwright parses a two-element test.use() array whose second entry is an object (a RegExp qualifies) as a [value, options] fixture tuple, so the console filter received a bare string and threw after every step had passed; one combined 400|401 pattern is unambiguous. Both specs are green under run_e2e.sh (12 passed), which is what deletes the weak-password bullet from rewrite-status. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PWTJcCbpQdzWLMVNg1QaLb
… dialog un-flag bug Adversarial-review batch on the branch diff. Every issued token now carries a fingerprint of the password hash (issue_refresh_token is the one mint; stubs/ninja_jwt grows Token.get), checked at authentication and refresh — a change or reset evicts the attacker who knew the old password and holds cookies, this slice's own threat model. The change endpoint re-mints the caller's cookies so changing your own password keeps you signed in; claimless tokens are refused by the same comparison, never grandfathered (ADR 0017). The reset request matches either email column exactly as login does (payroll-only staff could otherwise never reset, silently) and queues the Gmail send — synchronous sending ran only for matched addresses, making latency and a Gmail outage's 500 an account-existence oracle. SSE streams render the typed 403 instead of 500-looping a flagged EventSource. Frontend: a patch carrying a password now always carries the checkbox state — the dirty-only diff silently un-flagged an already-flagged account on a temp-password reissue; the auth screens share one AuthCard/PasswordField/FormAlert instead of three drifting copies, and the typed-403 predicate lives with its siblings in error-message.ts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PWTJcCbpQdzWLMVNg1QaLb
…link host The high-effort review pass on the full branch plus CodeRabbit, verified finding by finding. The dev/E2E seed no longer flags its staff (the flag now locks sessions, and those logins exist to act AS someone with the printed password); flag_weak_passwords documents that it flags EVERYONE and confines live sessions immediately. Re-entering the current password no longer satisfies a forced change. The change endpoint's refusals are declared 400s (PasswordErrorOut, shared with confirm) so the wire contract carries their shape and no AppError row records an expected refusal. The reset link pins its host to settings.APP_DOMAIN — ALLOWED_HOSTS accepts localhost and USE_X_FORWARDED_HOST let an anonymous caller poison a victim's genuine reset email with a dead link. One definition each for concepts that had grown twins: the login-email match (Staff.objects.sole_login_match, shared by the login backend and the reset request), the fingerprint comparison, and the typed-403 body. The stale-fingerprint refresh no longer clears cookies (a racing 401 could delete the session the changer was just re-minted). /reset-password joins route_reachability's entry list (reached only by the emailed link); the forced change screen gains sign-out and forgot-password exits; the deep link that started a flagged session survives through the forced change. The fleet-wide one-time re-login at deploy is recorded in rewrite-history. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PWTJcCbpQdzWLMVNg1QaLb
CI failed the outbox assertions with the request logging QUEUED and a 200: on the runner, .delay() published to the live redis service instead of running inline, while the identical invocation runs eagerly on a dev box. The repo already knew better — eager .delay() is "a property of the test settings, not of the product" (test_job_files_api.py) — so the endpoint tests now capture .delay's arguments (the queued job IS what the endpoint owes), and the task's own contract (recipient, subject, link, body) gets a direct-call test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PWTJcCbpQdzWLMVNg1QaLb
Written when it was "v2's only Google client" (its own comment), the command carried a private --credentials path and its own delegation checks. That premise ended when the Gmail sender landed: credentials now come from apps/core/gauth.py like every other Google entry point (GCP_CREDENTIALS key file, delegated subject with its fail-loud resolution), leaving from_service_account_file with exactly one call site in apps/. The Drive client construction stays in the command — clients are domain-owned the way gmail.py builds its own; credentials are what must never fork (ADR 0039). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PWTJcCbpQdzWLMVNg1QaLb
…026-08-31) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wbe63GrSY11WxJm6t2T5sS
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 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. Comment |
The 2026-08-31 incident class: DROPBOX_WORKFLOW_FOLDER pointed one directory above the Job-* tree, so every attachment 404d and new job folders spawned at the Dropbox top level while every daemon reported healthy. Three legs: reconfigure reads the operator's value back instead of reverting it to the empty instance dir, verify-instance.sh gates on every JobFile row resolving on disk, and check_jobfiles names the wrong-root cause first when nothing resolves. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wbe63GrSY11WxJm6t2T5sS
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wbe63GrSY11WxJm6t2T5sS
dw-run.sh sources the instance .env, so an unquoted space-containing workflow path (the real MSM value) aborts the source — the template now renders the value quoted, which read_env_value already strips on the reconfigure read-back. check_jobfiles resolves through job_file_full_path so a row the endpoint refuses cannot pass the check, and counts root-escaping rows as failures (the try/except movement in code-quality.md is this catch). Comment adjacency in verify-instance.sh restored, rationale comments carry their Fable: provenance, and the template's .env.example sync rule is honoured. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wbe63GrSY11WxJm6t2T5sS
corrin
commented
Aug 31, 2026
OwnerAuthor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Prod bug report 2026-08-31 after the flip: attachment "view" is gone. v1's attachments tab rendered a clickable thumbnail per file; v2's
JobAttachmentsTab.tsxrenders only download/delete icons and nothing infrontend/src/calls the portedgetJobFileThumbnailendpoint. This records the porting gap as a DEFERRED task indocs/rewrite-status.md(the download 404 is a separate ops issue on the prod host, diagnosed in-session, no code change).🤖 Generated with Claude Code
https://claude.ai/code/session_01Wbe63GrSY11WxJm6t2T5sS