Implement B01-B06 with regression coverage and retained validation evidence. Preserve the production ingress preflight; F04 runtime verification remains open.
42 KiB
Independent validation of the Jobjakt audit
Validated on 2026-09-07 against application commit 7d641edc7bca866ccf06012897ba1d107c07e59f. This is a verification and planning pass. No application behavior, schema, deployment configuration or production data was changed. The original audit is preserved.
Subsequent implementation status, 2026-09-09: B01 HTTP import enforcement, B02 account/session isolation, B03 shared PDF admission, B04 authenticated rate partitioning and B05 spreadsheet-safe CSV are complete locally. B05 verification passed 902 selected backend tests, 298 frontend tests, 18 browser tests, nine B04 HTTP scenarios and the unchanged B03 admission probe. Release builds and targeted lint pass; the existing R01a TypeScript failure remains. The historical analysis below describes the pre-fix baseline. F05 is resolved locally under its defined export policy; F08/V01 and F06 are resolved locally. F11 remains open for B07/IMAP and deployment evidence. F03 admission is resolved locally, with R04 containment/capacity evidence still open. B06 was subsequently authorized; see the separate progress and remaining gates below.
B06 runtime follow-up, 2026-09-09: Report and operator handoff. Implementation complete; full runtime/production verification incomplete; F04 open. Fresh 166 focused backend, 3 Compose, 6 Production HTTP and 9 B04 HTTP checks pass. Public DNS/valid HTTPS are now observed; direct ports 3000/5202/8080 are unreachable from this workstation, without proof of the rejecting layer. HTTP redirects to unreachable HTTPS port 444. Docker's inference socket failure still blocks actual Compose/Traefik/nginx tests; production peers, backend client IP and host/provider firewall remain unverified. No SSH alias/user was found in permitted non-secret sources. B01–B05 preserved; B07 not started.
Verdict: Suitable for private beta only. Keep participation controlled, with informed testers, an operator recovery path and paid checkout disabled until its gates pass. This is not approval to expose the current import/PDF surfaces to unrestricted registration. The evidence does not establish a universal tenant authorization failure that would make all external use inappropriate, but it does establish account-switch corruption, silent lost updates and insufficient abuse containment. Those prevent a public free beta. Paid use adds unresolved billing/deletion and operational proof requirements.
Confidence in the original audit: 7/10. It finds substantial real problems, provides useful source locations, and often labels unverified conditions honestly. Its 416-row coverage inventory is a map of evidence and open questions, not 416 completed validations. Severity is too broad for CSV, calendar and privileged-support concerns; the account-switch consequence is understated; the import address filter has a simpler bypass than the reported DNS race. Its implementation order puts recovery too late and puts identity isolation too close to a general UI cleanup.
- Validated implementation queue, ticket sizes, dependencies and handoff
- Evidence, commands, limitations and reproduction instructions
- Independent browser/API observations
- Independent .NET boundary observations
Audit confidence and method
Read the original README, findings, coverage, verification, resilience, product-operations, threat-data, source-index, break-jobjakt.mjs, retained break results and relevant docs/todo/work.md items. Inspected the implementation and tests behind the high-priority claims, rather than accepting their descriptions. The pasted expanded audit request provides background; the current validation-only instruction controls this pass.
Fresh verification: 149 backend tests passed in two disjoint targeted runs (70 + 79); 13 interview frontend tests passed; standalone TypeScript failed with the exact original error. Independent probes used synthetic SQLite databases, real local HTTP endpoints, two browser tabs, the real PDF exporter with three inert child executables, and a real .NET host with a one-shot database fault. Existing original full-suite/build results are historical evidence, not new executions. No live Stripe, SMTP, DNS rebinding, production host, production database, external website or cloud metadata endpoint was contacted.
The original harness is useful observational evidence, not a regression gate: recording a failed expectation does not itself make its exit code fail. Its simultaneous HTTP saves do not force both EF contexts to load the same revision. Its repeated POST does not establish an advertised idempotency contract. Its combined overflow/axe result conflates layout and accessibility. The independent probes add a deterministic two-context schedule, a UTC calendar control, actual cross-account saving, and process/fault boundaries. These probes also collect observations; implementation tickets must turn the relevant failures into assertions.
Every original P0/P1 finding
There are no original P0s and 11 original P1s, including conditional P1s. None is omitted below. Classifications distinguish a proven implementation defect from its unverified production impact. No new P0 is supported.
F01 — stale saves and revision history
Confirmed. Retain P1. The HTTP stale save returned 200 and replaced the newer state. Two independently preloaded real SQLite contexts both saved revision 1; the final row was Writer B/revision 2 and history was [1, 2, 2]. This is recoverable only in part: settings snapshots exist, but variant names are not included in those snapshots, and duplicate revision numbers make restore identification ambiguous.
The root cause is correct: SaveAsync increments an ordinary integer after reading, SaveRequest supplies no expected revision, and the model/index does not make the update conditional. A database transaction alone will not reject a stale client. Career Profile child/history writes already have a transaction; their missing precondition and the later legacy user projection are separate remaining problems. Job full-document updates likewise have no client revision precondition.
Corrected fix: define a small revision contract and implement it aggregate by aggregate. Atomically compare expected revision and increment on update; make history insertion part of that transaction. Reconcile existing duplicate labels without discarding snapshots before adding uniqueness. Retain the draft on 409/412; do not automatically retry it over newer content. StructuredCvProfile.version is a document schema version, not a concurrency revision: introduce a distinct field. Include restore and every mutation capable of changing that aggregate; do not assume one PUT covers status patches, admin undo or generated drafts.
Acceptance: the proposed tests are appropriate but require deterministic barriers/two contexts and both supported relational providers, not an in-memory provider or two loosely timed requests. Assert one winner, no partial children/projection/history, a preserved losing draft, unambiguous restore, delete-versus-save behavior and an older open frontend receiving a safe refusal. Do not promise transparent compatibility for old unconditional writers while claiming lost updates are prevented. Queue: D01–D04.
F02 — billing and account deletion
Mostly confirmed. Retain P1 for paid operation; provider effects need runtime verification. Checkout and the Stripe gateway create sessions without a durable purchase-attempt identity. Two requests can create two sessions; that is not proof of two charges, since completion still matters. The webhook verifies the signature, fetches current subscription state and checks ownership/price: ordinary sequential replay is already handled better than an event-order-only design. Identity's concurrency stamp also means some races fail and retry rather than blindly overwrite.
The remaining cause is split subscription/role updates plus an account model that does not reconcile all relevant subscriptions. An active subscription and a different cancelled subscription for the same account cannot be resolved reliably by treating each event's subscription as the entire account truth. Deletion removes identity/billing IDs without a billing cancellation stage. Deletion admission is disabled by default; do not describe an active paid deletion incident as observed.
Corrected fix: purchase-attempt idempotency, one explicit entitlement derivation, atomic local subscription/role state and retryable reconciliation. Start with a guarded synchronous handler and provider retries if that meets the contract. A new event inbox, broker or general outbox is not intrinsically required; durable receipt is required before acknowledging work that will happen asynchronously. Reuse existing deletion stages, preserve billing identifiers until resolution, and initially reject/defer deletion when active or uncertain billing cannot be resolved. This containment can precede full cancellation automation.
Acceptance: existing billing tests cover configured price, rejected product ID, signed sequential lifecycle/replay and bad signature; they do not prove concurrent purchase/deletion correctness. Add crash-between-local-writes, two subscriptions, delayed conflicting events, retry after an unknown checkout result, pending checkout versus deletion, unavailable provider and verified cancellation tests. Follow with an authorized Stripe sandbox run. Queue: P01–P03.
F03 — PDF host exhaustion
Remediation update, 2026-09-08: B03 closes shared admission and normal failure/cancellation lifecycle defects. Default one active render per API process, one per document owner, zero queue, validated configuration and safe 503/504 behavior now have permanent before/after and multi-route regression evidence. The original finding below remains historical; F03 is only partially resolved until R04 proves deployment containment and capacity.
Confirmed missing admission control. Retain P1 for an untrusted public audience. The singleton exporter starts one child process per call. Three calls through that actual implementation started three inert child processes while all were held open. A per-process 60-second timeout bounds duration, not total concurrency. The authenticated variant export has no PDF rate policy. Other PDF endpoints share this exporter, so endpoint-only patches leave bypasses.
Root cause is shared resource admission and lifecycle, not a proven Chromium exploit. Root container execution and sandbox-disabled flags increase containment concerns but do not establish RCE. Corrected fix: a small configurable shared concurrency bound, bounded or zero waiting, cancellation/cleanup, owner burst limits and host resource limits. Preserve legitimate downloads and distinguish admission denial from renderer failure. Measure before splitting the renderer into another service; no durable queue is required for request-scoped exports. Per-process admission is sufficient only for the current process topology; account for additional API replicas explicitly.
Acceptance: retain the proposed fake-process test, cover all exporter entry points, cancellation, timeout and partial output cleanup; then a small controlled host test measures RSS/process count with container limits. The three-process probe proves missing serialization, not an exhaustion threshold or safe production capacity. Queue: B03, R04.
F04 — forwarded headers and direct ingress
Needs runtime verification. Retain conditional P1. Compose publishes nginx on host port 3000; nginx forwards incoming forwarding headers while the backend trusts the nginx network. This is a real unsafe assumption if that path is externally reachable without the expected sanitizing proxy. A canonical Host header is not caller authentication.
The original root cause and qualified recommendation are sound. Do not assert that the deployed firewall is bypassed: its rules/topology were not inspected. Verify both the direct and normal routes, IPv4/IPv6 and effective backend client IP. Then close the direct route or sanitize at the correct trusted hop. Loopback binding is appropriate only if the upstream proxy is local. Avoid a speculative proxy redesign. The proposed isolated topology test is necessary, followed by operator evidence from the actual host. Queue: B06; it gates exposed release, not local source work.
Subsequent B06 status: Peer enforcement/configuration changes are implemented and native API regressions pass. F04 is not resolved: real nginx/container execution is blocked and actual host/proxy evidence is still required. Historical findings above remain preserved.
F05 — spreadsheet formula injection
Confirmed. Downgrade P1 → P2. A harmless title =1+1 remains a formula in CSV. Esc performs CSV syntax quoting, which does not change spreadsheet cell interpretation. Opening attacker-influenced imported content in a spreadsheet is a plausible additional user step, but no command execution, credential disclosure or server compromise was demonstrated. The current evidence does not justify a blanket P1.
The root cause and small fix are correct. Use one text-field policy, preserve JSON/raw portability, distinguish real numeric fields and verify leading whitespace/control characters and delimiters through a CSV parser. A huge generic export framework is unnecessary. The proposed acceptance needs a harmless manual spreadsheet check because parser round-trip alone does not establish spreadsheet interpretation. Fix early because it is cheap and protects a shipped export; it is not an independent reason to prohibit all external use. Queue: B05.
Subsequent resolution, 2026-09-09: B05 completion preserves this historical finding and closes the reproduced export failure locally. Actual Excel before/after changes =1+1 from an evaluated formula to apostrophe-prefixed literal text; all 13 text columns share the tested policy. Typed cells and stored/JSON data remain unchanged. UTF-8 BOM plus byte-preserving browser download fixes observed Norwegian decoding. LibreOffice runtime and arbitrary spreadsheet reprocessing are not claimed verified.
F10 — aggregate storage and resource growth
Mostly confirmed. Retain P1 for storage/admission exposure; split off P2 duplicate-intent and unmeasured scale work. Attachment admission sums current rows before staging/writing; concurrent uploads can both pass. It omits generated exports/artifacts/package copies. Account export streams the ZIP file but materializes collections and serializes full JSON byte arrays. It is incorrect to call the entire ZIP an in-memory buffer; peak per-table memory remains a real concern.
No production disk exhaustion or 100k-row memory threshold was measured. Two job POSTs with the same unrecognized Idempotency-Key produced different IDs. That proves no implemented key contract, not an HTTP violation or a need for unique company/title jobs. Repeated applications can be legitimate. Idempotency belongs to a specific creation intent and does not replace quotas.
Corrected fix: hard admission bounds first; reuse the existing owned-file inventory for accounting/reconciliation. Use a database-safe owner reservation, not a process-local quota lock. Include crash/release handling and generated categories. Stream/batch large exports when measurements require it. Do not silently prune user history to meet a new limit. Separate job creation retry semantics from this resource ticket and from F01.
Acceptance: simultaneous near-limit reservations, file/DB failure cleanup, generated artifacts and multi-instance accounting; synthetic bounded-memory exports; existing over-quota accounts remain readable/exportable. Same-intent/different-payload/retry tests belong to D04. Do not require every query to be redesigned before safe initial bounds ship. Queue: D04–D07; PDF overlap belongs only to B03.
F11 — outbound URL boundary
Remediation status: HTTP B01/V02 resolved in the working tree on 2026-09-08; connection-time regression evidence and limitations. Overall F11 remains partially remediated because IMAP B07 is unstarted. The following finding/evidence is preserved from the original validation.
Confirmed boundary defect; retain P1. Production reachability is unverified. In addition to the source-confirmed validation/connection DNS gap, the real import validator accepts IPv4-mapped IPv6 representations of denied IPv4 addresses. A fake HTTP handler was invoked for mapped private, link-local and metadata addresses, while ordinary IPv4 representations were blocked. No network socket was opened by that probe. This bypass does not need changing DNS answers.
The original explanation is incomplete: normalize mapped addresses before classification, and bind address validation to the address actually connected. The registered HTTP handler resolves separately after validation. IMAP also validates then calls ConnectAsync(host, ...); it already normalizes mapped IPv4 in its guard, so do not attribute the import-specific normalization bug to IMAP. It still needs connection-time enforcement.
Corrected fix: protect the actual socket boundary while retaining hostname/TLS validation, redirect rejection, size limits and cancellation. A proven restrictive egress layer can satisfy the boundary, but none was verified here. Reuse a narrow address policy only where the policies are genuinely the same; do not build a universal network platform or disable TLS checks. The original proposed mixed/mapped/DNS/TLS acceptance matrix is good, but it was a proposed test, not proof that the existing mapped check worked. Queue: B01 HTTP, B07 IMAP.
F12 — backup/restore and rollback
Mostly confirmed. Retain P1 as a release/recovery gate. The daily runner uses SQLite VACUUM INTO, a consistent database snapshot. The pre-deploy live-volume tar is not equivalent; concurrently changing DB/WAL/files have no demonstrated common recovery point. MariaDB's transactional SQL dump does not make separate file archives transactionally consistent. No corrupt backup was reproduced or alleged.
The failure trap restores previous images, not the old database schema. This is safe for some additive changes and unsafe for incompatible migrations. Do not disable every image rollback indiscriminately. Corrected fix: verified recovery sets, a deliberate file checkpoint/write pause, checksums and an isolated restore drill; attach schema compatibility to rollback decisions. Keep keys and deletion tombstones recoverable with appropriate independent custody. Reuse existing backup machinery.
Acceptance: restore while the source has synthetic writes, validate integrity/ownership/files/decryption/tombstone replay, kill incomplete backups and prove compatibility with the previous binary. The original tests are sufficient in intent but unexecuted for deployment recovery. Run this before the first new schema migration, not after concurrency, billing and quota migrations. Queue: R02–R03.
F13 — privileged support, auditing and retention
Partially confirmed. Downgrade engineering finding P1 → P2. AdminAuditController is explicitly Admin-authorized. Cross-owner activity and undo are deliberate admin capabilities; they are not a normal-user tenant escape. Its returned private event fields and absence of a dedicated security event ledger support minimization/audit work. Infrastructure root access and absence of a support/impersonation role are not vulnerabilities by themselves.
The finding combines three different things: admin product permissions, accountable security mutations, and owner/legal retention decisions. Corrected fix: document the existing admin boundary, minimize fields where they are unnecessary, add safe actor/action/target/outcome events for high-impact changes, and use recent authentication where warranted. Do not invent impersonation, a role hierarchy or tamper-proof logging claims. A restricted support role is justified only by an actual support use case.
Acceptance: unauthorized verbs remain denied, intentionally authorized admin verbs remain functional, privilege revocation takes effect, and audit metadata excludes arbitrary content/secrets. Off-host audit custody and policy approval require operator evidence; unit tests cannot certify them. Retention/deletion policy remains a public/paid operational prerequisite without converting every suggested admin improvement into P1. Queue: W04 and P03 policy dependency.
F20 — AI worker failure containment
Confirmed when enabled. Retain conditional P1. A real host running the actual worker with concurrency 1 stopped after a single injected database timeout. With concurrency 2, one loop failed but the host and combined task did not stop during the observation window: Task.WhenAll waits on the surviving loop. The audit misses this second mode, where capacity silently shrinks rather than immediately stopping the host.
The cause is failure escaping the loop boundary, not simply the host exception policy. Compose defaults this worker off, so a default deployment outage is not established. Corrected fix: catch/classify at the loop, bounded retry/backoff, explicit degraded health and lease recovery. Do not globally ignore background-service exceptions or hide a dead worker behind a healthy API.
Acceptance: one- and multi-worker tests, claim/owner/result persistence failures, cancellation, recovery and lease expiry, followed by bounded outage drills. The local probe confirms host/worker behavior, not endpoint availability during a deployed DB outage or eventual recovery. Keep the feature disabled until those acceptance tests pass. Queue: R05.
F21 — calendar/timezone
Mostly confirmed. Downgrade P1 → P2 for the currently implemented workflow. Offset-bearing input lost offset information after SQLite reload; the reproduced spring value exported 09:00Z instead of 07:00Z, and the two autumn offsets collapsed. However, the UTC input control preserved 07:00Z. This is not evidence that every production UTC event shifts. Server timezone affects DateTime parsing; production timezone/provider behavior was not exercised.
More fundamentally, the normal job editor and Interview Prep send date-only follow-up values. Calendar conversion turns an unspecified date into a UTC timed event. The independent date-only control became midnightZ, which can display on a different local date. The root cause is a missing date-versus-instant contract, not a universal need for named-zone recurrence.
Corrected fix: preserve the current date-only product semantics and emit all-day follow-up/deadline events in ICS and provider links. Specify UTC normalization for any genuinely timed API field. Do not infer historical timezones or shift stored dates globally. A timed appointment/recurrence feature needs its own product scope and evidence. Current impact is workflow correctness; P1 would need evidence that a promised timed scheduling flow is routinely losing important appointments.
Acceptance: date-only all-day values and exclusive end dates, UTC control, offset cases, both providers and server timezone permutations. The original offset tests are valuable but insufficient without the date-only control. Queue: W01, with no broad timestamp migration assumed.
New findings and severity increases
V01 — account A's open form can overwrite account B (F08 raised to P1)
Remediation status: B02/F08/V01 resolved in the working tree on 2026-09-08. Server session precondition, frontend isolation, before/after regression evidence and rollout limits. The original finding below is preserved as historical evidence.
Confirmed with real UI and HTTP persistence. Tab A loaded account A's Career Profile and edited its full name. Tab B used the actual sign-out menu and signed into B. Tab A remained mounted; clicking its Save changes button returned 200. An independent API session read the A draft in B's stored profile, while A's stored profile remained unchanged.
auth.ts broadcasts same-window events; App does not synchronize the authentication boundary across tabs. api.ts uses shared current cookies/CSRF state. CareerProfilePage sends its retained local form to an endpoint whose owner is implicit in those cookies. Backend ownership checks correctly resolve B; the client submits A's content as B. This is not a direct-object-reference authorization bypass, and it is more serious than private text staying visible after logout.
Required scope: one account/session generation boundary; promptly clear/unmount private owner state on logout/account change, cancel or ignore late responses, and refuse dispatch from a form loaded under a different identity. Include a server-checked expected-owner/session precondition where needed to close the cookie-switch race; it can only reject a mismatch, never grant authority. Reading the latest localStorage identity at send time defeats the purpose: bind to the identity that loaded the form. Revision numbers alone do not solve this because A and B can have equal revisions. Tests must repeat this exact two-tab save and assert both accounts' stored profiles, not just navigation. Queue: B02.
V02 — mapped-address import bypass (F11 strengthened)
The new address-normalization proof above removes DNS timing as a prerequisite for passing the application guard. Track it under F11/B01, not as a second duplicate SSRF implementation project. F20's multi-worker failure mode likewise extends its existing ticket. No additional independent P0/P1 is claimed merely because an area lacks complete test coverage.
Other highlighted findings and negative evidence
| Area | Independent conclusion |
|---|---|
| F06 account/IP rate limiting | Confirmed P2; resolved locally by B04, 2026-09-08. Historical behavior: A's two exports made B's first return 429 because rate limiting preceded authentication. Authentication and endpoint authorization now resolve the final trusted resource principal before account partitioning; permanent real HTTP tests prove A/B independence, shared same-account sessions, invalid/forged credential handling and preserved IP policies. Deployment proxy reachability remains B06. |
| F09 draft recovery | Partially confirmed P2. The original user accepted an unload warning and refreshed while offline. Lack of durable recovery is real, but this is not a violated guaranteed-save contract. Preserve current dirty-state warnings; do not introduce persistent private browser drafts without an ownership/retention decision. W05 follows identity and revision work. |
| F14 accessibility | Confirmed P2, with a structural release acceptance gap. Shared contrast defects and a responsive preview scrolling defect reproduce; see the focused review below. Axe's serious label is not automatically product severity P1. |
| F15 export/cache/CSP | Owner checks and recent-auth account export are present. Missing no-store headers are hardening evidence, not a demonstrated cross-user CDN cache disclosure. Review sensitive responses and preview-compatible CSP without conflating it with V01. W03. |
| F17 TypeScript | Confirmed P2 CI gap, test-only diagnostic; exact explanation below. R01. |
| F19 provider fallback | Source confirms silent SQLite fallback for a missing connection or unknown provider. Keep P2 with a release-startup gate: production misconfiguration should fail closed. No proof the deployed instance points at the wrong database. R01. |
| Tenant/document authorization | Fresh owner-boundary checks denied B's access to A's variant/preview/PDF. Version listing returns 200 with [], which reveals no other-owner history and is not an IDOR. Acceptance should require no data/no effects rather than insist every safe empty listing be 404. Query filters and explicit owner checks remain important positive evidence. |
| Auth, admin, entitlement | Fresh authorization/session/role/identity tests passed. The Pro handler re-reads user roles/AI preference, rather than relying only on stale JWT entitlement claims. No newly demonstrated ordinary-user admin access or session-revocation bypass. Admin content access remains intentional and needs policy/minimization, not an automatic security rewrite. |
| AI and uploads | Privacy/ownership tests passed; AI output remains untrusted and must stay inside existing structured validation and review boundaries. Existing parser/file staging/type/size/owner checks constrain the obvious paths. No new cross-owner document download, executable upload or model-driven privileged action was demonstrated. Resource admission and URL boundaries remain the evidenced problems. |
| Migrations, secrets and deployment | Fresh migration-chain tests passed; no new destructive migration/data-drop defect was reproduced. They do not prove rolling back old binaries or restoring files/keys. The tracked-secret scanner and lock files are useful but do not certify Git history, deployed secrets or current dependency advisories. External/host verification remains open; no secrets or live environments were inspected. |
TypeScript and production release gates
Exact fresh error: src/interview-prep.test.tsx(120,10): error TS2571: Object is of type 'unknown'.
The test reads .content from mockedApi.post.mock.calls[0][1]; the generic Axios request body is unknown at that boundary. The test's earlier runtime matcher does not narrow that later expression for TypeScript. All 13 tests in the suite passed under Babel/Jest, which transpiles TypeScript without performing this static check. The installed Next runTypeCheck.js explicitly filters .test, .spec, __tests__ and __mocks__ diagnostics; Next build success is therefore consistent with standalone tsc failure.
The CI workflow runs frontend tests/lint/build/browser checks without an explicit standalone tsc --noEmit gate. This is a real configuration gap. The diagnostic is in a test and does not demonstrate a broken emitted production component. It would not alone justify an emergency production rollback or P1 incident, but the next planned release should have a clean explicit type gate. Correct the test's typed payload boundary narrowly; do not disable strictness, exclude all tests or use an unrelated framework migration. No fix was made in this pass.
Accessibility conclusions
The normal browser accessibility suite checks /, /login, /dashboard, /jobs, /kanban, /career, /settings and /admin/system. It forces English and waits primarily for body/main visibility. It does not ensure loaded content or open interactive states, and does not systematically cover dark mode or Bokmål.
Missing important coverage includes /career/builder, /career/builder/:id, actual profile/notes states, job detail/edit and interview interactions, public CV, registration/reset/second-factor flows, admin users/audit, menus/dialogs, errors/conflicts and mobile preview/edit transitions. A green suite is valid evidence for its sampled states, not accessibility certification for the product.
The fresh loaded-builder probe found light-mode success chips at 3.29:1 (#16a34a on white), including Saved and ATS chips. Dark primary text appears at 3.50–3.66:1 on the two dark surfaces. theme.ts reuses the accent as primary text across themes; outlined buttons/tabs/chips inherit these values. This proves a shared token/component relationship. Fix semantic foreground/background pairs and relevant MUI state overrides centrally, then verify existing surfaces; do not patch each page's color or add a new design-system framework.
The preview's scroll region also fails scrollable-region-focusable in the mobile-to-desktop transition. It is not present in every initial viewport/theme sample. The offending overflow region contains the rendered page iframe and has no explicit focusable region contract; CvBuilderEditor:480 is the corresponding preview container. Repair named region/focus/keyboard scrolling and verify actual behavior, rather than adding tabindex to every Box. The editor controls already include keyboard-capable template cards; do not label every custom card inaccessible without testing it.
Before public release, manually complete the primary tracking/profile/builder flow using keyboard only and NVDA or Narrator, including preview scroll, tab order, focus return, error/conflict announcements and reauthentication. Test 200%/400% zoom and 320 CSS-pixel reflow, text spacing, Windows forced colors, reduced motion, touch/keyboard alternatives to drag-and-drop, and real mobile keyboard overlap. No screen-reader/manual accessibility certification was performed here. Structural keyboard and focus failures take precedence over polish. Queue: A01–A03.
Shared root causes, without a generic overhaul
- Record preconditions: CV, job and Career Profile updates lack a client/server revision contract. Share the convention and error semantics; keep aggregate-specific transactions and conflict UI. Do not confuse this with schema version numbers.
- Identity lifecycle: cross-tab cookies, retained forms and late responses have no common account-generation boundary. Fix this before adding persistent drafts or rebuilding editors. It is not fixed by per-record revisions.
- Admission before side effects: PDF launch, file quotas and owner rate policies do not consistently enforce limits at the boundary that consumes the resource. Reuse existing services, with a shared exporter bound and database-safe storage reservations. An idempotency key limits repeated intent; it is not a resource quota.
- Validated target versus connected target: import and IMAP share a time-of-check/time-of-use boundary, while only import misses mapped-address normalization. Share only the proven common policy/connection principle.
- Split local/provider state: billing/deletion and profile legacy projection need explicit commit/recovery boundaries. They do not all require a new event bus. The profile projection can usually join its transaction; external billing cannot.
- Shared visual semantics: theme tokens create cross-page contrast failures; preview focus is a local structural component issue. Fix these before changing page layouts.
- Proof at the wrong layer: helper tests do not prove middleware order, a green shell does not prove loaded accessibility, a tar file does not prove restore, and a green build does not prove the standalone TypeScript program. Add the missing boundary tests rather than expanding every suite indiscriminately.
Confirmed launch blockers and release levels
For a public free beta, close V01/F08 identity corruption, F01 lost writes, F11 outbound target enforcement, F03 PDF admission and the concrete F10 storage admission gaps. Establish a verified recovery set/compatible rollback and confirm actual ingress/configuration safety. Resolve primary-flow structural accessibility failures and retain manual evidence. These are correctness/security/recovery gates; a broad UX overhaul is not required.
For a limited paid beta, additionally prove F02 purchase/entitlement/deletion handling in sandbox, a workable retention/deletion/support policy, verified key/data recovery and functioning enabled premium features. F20 blocks enabling its worker until containment/recovery tests pass. Keeping paid deletion disabled is not a complete long-term paid-account policy.
For a public paid launch, complete representative end-to-end paid journeys, accessibility/manual verification, support and recovery drills, measured capacity at the intended exposure, and production ingress/secrets/release evidence. Exact public host, Stripe, mail, off-site backup, advisory and operator controls remain unverified. Their absence from this checkout is a missing release proof, not proof that deployed controls do not exist.
The rejected stronger verdict is public paid launch readiness today. Conversely, the evidence does not support demanding a new broker, renderer microservice, role platform, PWA, total page redesign or 100k-record optimization as prerequisites to a controlled beta.
Decisions Required From Connor
These decisions can be supplied when the dependent ticket starts; none is needed to read or approve this validation.
| Decision | Why the repository cannot decide it | Proposed default / dependency |
|---|---|---|
| Paid deletion timing | Whether deletion cancels paid service immediately or keeps access until period end changes provider calls, messaging and retention timing. | Immediate cancellation only on explicit delete confirmation; never erase billing identity before verified resolution. Decide before P03. |
| Recovery objectives and custody | Acceptable data-loss window, recovery time and who can recover encryption keys require an operator/business commitment. | Use 24-hour RPO and 4-hour RTO as proposed drill targets, not measured promises; approve or replace before R03. |
| Private device-local drafts | Persisting CV/profile content on shared devices is a privacy/product choice. | Keep memory-only drafts plus dirty-state warnings until an explicit opt-in, owner scope and expiry policy are approved. W05. |
| Version retention/over-quota promises | Pruning historical CV/profile versions changes users' restore expectations and paid-plan promises. | Preserve existing history; reject further growth with a recoverable explanation before silently pruning. Decide before D07 expands retention. |
| Operator content access and retention | Which support actions actually need private document/event content and how long account/export/deletion records remain are policy decisions. | Retain current admin-only boundary, minimize unnecessary content, add no impersonation role. W04/P03. |
Do not ask whether follow-ups should retain their existing date-only behavior, whether a concurrency token is needed, or whether to add a type gate: the repository/evidence answer those engineering questions. New timed scheduling is a separate future product request.
Recommended implementation order and first handoff
Use the exact ticket order and permitted parallel branches in implementation-queue.md. The essential order is: contain security/identity/resource defects → establish release and recovery proof → introduce data-integrity migrations → correct paid lifecycle → accessibility foundations → workflow corrections → broader UX and measured operations. Independent theme/type tests may run earlier; they do not authorize skipping a dependency.
The original first handoff was B01 only: HTTP import target enforcement and its regression tests. B01–B05 are now complete locally; see the linked completion records above. B06 verify and close ingress bypass path was subsequently authorized; its implementation is present, with runtime/host gates still pending. Do not start B07. Completing B01 does not close F11's separately scoped IMAP/deployment work; B03 does not close F03's R04 containment/capacity work; B04 does not close B06's deployment ingress proof.
Stop conditions
- Do not close a finding from source inspection alone when its acceptance requires a socket, two contexts, browser tabs, provider sandbox or restored database.
- Do not start schema-changing tickets until R02/R03 produce a restorable set and an explicit old/new binary compatibility decision. Never delete duplicate CV snapshots merely to make an index migration pass.
- Do not enable open registration/exposed costly features while their boundary/admission findings remain open. Keep AI workers and paid lifecycle operations gated until their own tests pass.
- Do not begin page redesign or persistent draft work while shared identity, revision or accessibility primitives it depends on are unresolved.
- Stop a rollout on owner-state contamination, silent conflict overwrite, unexpected entitlement divergence, unresolved billing before identity deletion, failed restore/integrity, unbounded resource admission, or primary-flow keyboard regression. Preserve evidence and existing data; use the rehearsed compatible rollback or forward repair.
- The validation itself authorized no implementation. Subsequent explicit instructions authorized B01–B05, now complete locally. B06 was subsequently authorized and implemented with verification gates still pending. Stop before B07 or any other ticket without a further instruction.