Skip to content

feat: migrate investigation architecture - #42

Merged
devjusty merged 92 commits into
mainfrom
investigation-architecture
Sep 24, 2026
Merged

devjusty merged 92 commits into
mainfrom
investigation-architecture

Conversation

@devjusty

Copy link
Copy Markdown
Owner

Summary

  • Introduce domain-first investigation lifecycle, contracts, adapters, persistence, and application seams.
  • Migrate investigator and investigations workflows to command-backed read models with recovery, evidence provenance, and persistence fallback.
  • Add investigator/admin shells and evidence-first admin inbox while removing obsolete UI adapter code.
  • Update Vitest/Storybook providers and document the remaining AdminPage test blocker.

Test Plan

  • pnpm --filter frontend test (552 passed)
  • pnpm --filter frontend typecheck
  • pnpm --filter frontend build
  • pnpm --filter frontend build-storybook
  • Server tests (162 passed)
  • Fallow audit and final review
  • Browser walkthroughs beyond production smoke remain environment-dependent

Known unrelated issue: server Jest configuration remains documented in docs/product-roadmap.md.

@netlify

netlify Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for wp-json-discovery ready!

Name Link
🔨 Latest commit c2bd1cb
🔍 Latest deploy log https://app.netlify.com/projects/wp-json-discovery/deploys/6ab52987fcdc740008a60a72
😎 Deploy Preview https://deploy-preview-42--wp-json-discovery.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ddae072396

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread frontend/src/App.tsx Outdated
</Suspense>
);
return (
<InvestigatorShell readModel={investigatorReadModel} commands={{ onSectionChange: handleInvestigatorSectionChange, onRetry: retryInvestigatorCapability }} activeSection={activeInvestigatorSection} contentLandmark="main" contentMode="legacy" headerActions={headerActions}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Render the sections exposed by investigator navigation

On the scan route, contentMode="legacy" suppresses InvestigatorShell's canonical section content while ScanPage forwards the selected findings, evidence, assets, history, or tools ID to the legacy ScanSectionContent; that switch handles none of those IDs and returns null. Selecting most of the newly advertised primary sections therefore produces a blank report area. Render the canonical shell content or map/hide these sections until the legacy renderer supports them.

AGENTS.md reference: AGENTS.md:L54-L54

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 41e7a7b. Investigator routes now render canonical section content for all advertised sections instead of sending those IDs to the legacy renderer. Added navigation/content regressions.

Comment on lines +150 to +152
if (snapshot.investigation === undefined) {
throw new ContractInvalidError('Anonymous investigation is missing validated full state');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Migrate legacy anonymous snapshots before resuming

Every anonymous snapshot written before this commit lacks the new top-level investigation field—the previous serializer stored only version, revision, domain, and record. Rejecting those otherwise-valid snapshots here means an upgrade makes all existing anonymous investigations impossible to resume or claim, even though their session record contains enough state for the existing recovery mapper to reconstruct the investigation.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 41e7a7b. Legacy anonymous snapshots are reconstructed from their validated session record during strict load/claim, preserving upgrade continuity while malformed payloads still raise ContractInvalidError.

Comment on lines +225 to +226
const session = record.latestSession;
if (session?.investigationState !== undefined) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reconstruct legacy authenticated sessions on read

Authenticated session rows created before this migration legitimately omit the newly optional investigationState, and the server still returns those stored snapshots unchanged. The investigations list can therefore show an old row as resumable, but get() always reaches this branch and fails with contract-invalid; reconstruct the legacy state on read or migrate stored sessions so existing investigations remain usable.

AGENTS.md reference: AGENTS.md:L61-L61

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 41e7a7b. Authenticated records without investigationState are reconstructed from validated legacy session capability data during read/list/resume.

const selection = normalizeSelection(capabilities);
const { startInvestigation: startCommand } = await import('../application/investigation/start.ts');
const result = await startCommand({
domain: { submittedUrl, normalizedUrl: normalize(submittedUrl) },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Accept URL-shaped input in authenticated starts

When a signed-in user submits a value supported by DomainForm, such as https://www.example.com/, this preserves the raw value as submittedUrl and sends it through remoteStart. The server's sanitizeDomain accepts only bare hostnames, so protocol/path inputs are rejected; a bare www.example.com is instead stored with www and then rejected by the client's identity assertion because normalizeDomain removed it. As a result, common inputs that work for anonymous scans fail before authenticated capability execution.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 5f44436. Authenticated starts now normalize URL-shaped inputs to hostname identity consistently with the server while preserving submittedUrl and redirectChain. Added protocol/path/query/www regressions.

Comment on lines +43 to +44
const investigation = authenticated && dependencies.remoteStart
? await dependencies.remoteStart(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply local fallback when remote allocation fails

For an authenticated start, the initial remote allocation is awaited before createPersistenceContext installs its remote-to-local fallback. If the API or database is unavailable during this POST, the command rejects without allocating, saving, or running a local investigation, so the promised offline/local-only behavior only covers later save failures and not the first persistence operation.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in c2bd1cb. Initial authenticated allocation falls back locally only for network, retryable, or 5xx failures; validation, auth, conflict, and other 4xx/contract errors propagate.

Comment on lines +17 to +18
startedAt: timestamp,
completedAt: status === 'running' ? null : timestamp,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve actual session completion timestamps

All terminal sessions are serialized with completedAt equal to the investigation's original createdAt, regardless of how long execution or a later resume took. This records every completed or failed attempt as zero-duration and corrupts persisted timeline data; use the lifecycle completion time or at least the investigation's latest updatedAt for terminal completion.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 41e7a7b. Session serialization now uses the investigation latest update/lifecycle completion time for terminal completedAt instead of the original createdAt.

Comment on lines +118 to +121
const hydrated = await Promise.all(summaries.map(async summary => {
if (!summary.latestSessionId) return summary;
try {
const value = await source.get(summary.id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return investigation summaries without per-item hydration

Whenever a summary has latestSessionId, list() issues an additional full-state GET for that investigation even though InvestigationSummary already contains every field consumed by the investigations table. Accounts with substantial history therefore incur an N+1 request pattern and download potentially large evidence payloads; moreover, one failed hydration rejects the entire list instead of leaving that row as its valid summary.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 41e7a7b. List returns validated summaries directly without per-item hydration; full state is fetched only through explicit get/resume paths, and summary rows survive individual races/not-found responses.

@gitguardian

gitguardian Bot commented Sep 24, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 2 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
37574786 Triggered Generic Password 41e7a7b server/src/utils/domain.js View secret
37574786 Triggered Generic Password c2bd1cb frontend/src/services/investigationSession.js View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@devjusty
devjusty merged commit 7a22689 into main Sep 24, 2026
11 of 12 checks passed
@devjusty
devjusty deleted the investigation-architecture branch September 24, 2026 14:16
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