my-frontend-observer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +69 -0
  3. package/dist/application/browserCaptureService.d.ts +11 -0
  4. package/dist/application/browserCaptureService.js +12 -0
  5. package/dist/application/browserCaptureService.js.map +1 -0
  6. package/dist/application/observationPersistence.d.ts +59 -0
  7. package/dist/application/observationPersistence.js +78 -0
  8. package/dist/application/observationPersistence.js.map +1 -0
  9. package/dist/artifacts/artifactWriter.d.ts +25 -0
  10. package/dist/artifacts/artifactWriter.js +68 -0
  11. package/dist/artifacts/artifactWriter.js.map +1 -0
  12. package/dist/artifacts/types.d.ts +17 -0
  13. package/dist/artifacts/types.js +2 -0
  14. package/dist/artifacts/types.js.map +1 -0
  15. package/dist/browser/chromiumAdapter.d.ts +21 -0
  16. package/dist/browser/chromiumAdapter.js +150 -0
  17. package/dist/browser/chromiumAdapter.js.map +1 -0
  18. package/dist/browser/evidenceCapture.d.ts +19 -0
  19. package/dist/browser/evidenceCapture.js +201 -0
  20. package/dist/browser/evidenceCapture.js.map +1 -0
  21. package/dist/browser/types.d.ts +22 -0
  22. package/dist/browser/types.js +2 -0
  23. package/dist/browser/types.js.map +1 -0
  24. package/dist/cli.d.ts +7 -0
  25. package/dist/cli.js +216 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/domain/completion.d.ts +30 -0
  28. package/dist/domain/completion.js +22 -0
  29. package/dist/domain/completion.js.map +1 -0
  30. package/dist/domain/diagnostics.d.ts +17 -0
  31. package/dist/domain/diagnostics.js +55 -0
  32. package/dist/domain/diagnostics.js.map +1 -0
  33. package/dist/domain/evidence.d.ts +27 -0
  34. package/dist/domain/evidence.js +55 -0
  35. package/dist/domain/evidence.js.map +1 -0
  36. package/dist/domain/identity.d.ts +13 -0
  37. package/dist/domain/identity.js +37 -0
  38. package/dist/domain/identity.js.map +1 -0
  39. package/dist/domain/schema.d.ts +111 -0
  40. package/dist/domain/schema.js +126 -0
  41. package/dist/domain/schema.js.map +1 -0
  42. package/dist/index.d.ts +21 -0
  43. package/dist/index.js +12 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/request/paths.d.ts +14 -0
  46. package/dist/request/paths.js +33 -0
  47. package/dist/request/paths.js.map +1 -0
  48. package/dist/request/request.d.ts +43 -0
  49. package/dist/request/request.js +174 -0
  50. package/dist/request/request.js.map +1 -0
  51. package/dist/safety/policy.d.ts +14 -0
  52. package/dist/safety/policy.js +81 -0
  53. package/dist/safety/policy.js.map +1 -0
  54. package/docs/ARCHITECTURE.md +85 -0
  55. package/docs/CI_CD.md +28 -0
  56. package/docs/COMMANDS.md +82 -0
  57. package/docs/CONTRACTS.md +54 -0
  58. package/docs/CURRENT_STATE.md +113 -0
  59. package/docs/DEVELOPMENT.md +65 -0
  60. package/docs/DOCUMENTATION_PRESERVATION_POLICY.md +33 -0
  61. package/docs/PROJECT_DESCRIPTION.md +1770 -0
  62. package/docs/PROJECT_MILESTONES.md +2073 -0
  63. package/docs/PROJECT_OVERVIEW.md +53 -0
  64. package/docs/QUICKSTART.md +35 -0
  65. package/docs/RELEASE.md +9 -0
  66. package/docs/ROADMAP.md +352 -0
  67. package/docs/SECURITY.md +33 -0
  68. package/docs/WORKFLOWS.md +51 -0
  69. package/package.json +46 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ## 0.1.0 - 2026-08-11
6
+
7
+ Runtime Observation Foundation. First public release.
8
+
9
+ - Local-first browser runtime evidence producer: a real `observe` CLI command
10
+ that launches Chromium under a loopback-only network safety policy.
11
+ - Explicit CSS-selector observation targets (`--target id=selector`,
12
+ repeatable).
13
+ - Viewport screenshot capture (`screenshot.png`).
14
+ - Bounded page evidence and bounded target evidence, with honest
15
+ unavailable/not-applicable/partial states when evidence cannot be
16
+ determined rather than guessing.
17
+ - Loopback/network safety enforcement (`http`/`https`, `localhost`/`127.x.x.x`/
18
+ `::1` only).
19
+ - Versioned, portable observation artifact: `manifest.json` + `screenshot.png`
20
+ written atomically per observation, artifact schema `1.0.0`.
21
+ - Validated as a packed npm tarball with a clean-consumer install-and-observe
22
+ smoke on Windows, Linux, and macOS.
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # my-frontend-observer
2
+
3
+ `my-frontend-observer` is the local-first rendered browser/runtime evidence
4
+ producer in the my-dev-kit ecosystem. Its durable product purpose is defined
5
+ in [docs/PROJECT_DESCRIPTION.md](docs/PROJECT_DESCRIPTION.md).
6
+
7
+ ## Current status
8
+
9
+ `v0.1.0`, Runtime Observation Foundation, is the current published release:
10
+ `my-frontend-observer observe` launches a real, sandboxed Chromium browser,
11
+ enforces a loopback-only safety policy, captures a viewport screenshot plus
12
+ bounded page/target evidence, and persists it as one portable
13
+ `manifest.json` + `screenshot.png` artifact.
14
+
15
+ Install:
16
+
17
+ ```powershell
18
+ npm install my-frontend-observer
19
+ npx playwright install chromium
20
+ ```
21
+
22
+ Setup for working from a source checkout instead:
23
+
24
+ ```powershell
25
+ npm install
26
+ npx playwright install chromium
27
+ npm run build
28
+ ```
29
+
30
+ Example use, against your own locally running frontend:
31
+
32
+ ```powershell
33
+ my-frontend-observer observe `
34
+ --url http://localhost:3000/ `
35
+ --viewport 1280x720 `
36
+ --target header=header `
37
+ --target main-content=main `
38
+ --output observations
39
+ ```
40
+
41
+ (From a source checkout, use `node dist/cli.js observe ...` instead.)
42
+
43
+ This prints a concise result (`Observation:`/`State:`/`Artifact:`/`Targets:`/
44
+ `Diagnostics:`) and exits `0` on a successfully persisted observation. See
45
+ [docs/COMMANDS.md](docs/COMMANDS.md) for the full flag reference.
46
+
47
+ Validation:
48
+
49
+ ```powershell
50
+ npm run typecheck
51
+ npm run lint
52
+ npm test
53
+ npm run test:browser
54
+ npm run build
55
+ npm run check:docs
56
+ ```
57
+
58
+ Planning authorities:
59
+
60
+ - [Project Description](docs/PROJECT_DESCRIPTION.md): complete durable product
61
+ intent and responsibility boundaries.
62
+ - [Project Milestones](docs/PROJECT_MILESTONES.md): complete ordered capability
63
+ design and cross-milestone rules.
64
+ - [ROADMAP](docs/ROADMAP.md): version-level requirements; v0.1 is released,
65
+ v0.2 is next.
66
+ - [Current State](docs/CURRENT_STATE.md): retained scaffold and release state.
67
+
68
+ No sibling ecosystem repository is a runtime dependency of the retained
69
+ foundation.
@@ -0,0 +1,11 @@
1
+ import type { NormalizedObservationRequest } from '../request/request.js';
2
+ import type { BrowserCaptureResult } from '../browser/types.js';
3
+ export type { BrowserCaptureResult, BrowserProvenance } from '../browser/types.js';
4
+ /**
5
+ * Minimum application-facing seam over the browser adapter: takes an
6
+ * already-validated request and returns the observer-owned browser-boundary
7
+ * result. Coordinates nothing beyond that single invocation - no page/target
8
+ * evidence, no artifact writing, no CLI reporting. Later batches extend this
9
+ * seam rather than calling the browser adapter directly.
10
+ */
11
+ export declare function runBrowserCapture(request: NormalizedObservationRequest): Promise<BrowserCaptureResult>;
@@ -0,0 +1,12 @@
1
+ import { captureViewport } from '../browser/chromiumAdapter.js';
2
+ /**
3
+ * Minimum application-facing seam over the browser adapter: takes an
4
+ * already-validated request and returns the observer-owned browser-boundary
5
+ * result. Coordinates nothing beyond that single invocation - no page/target
6
+ * evidence, no artifact writing, no CLI reporting. Later batches extend this
7
+ * seam rather than calling the browser adapter directly.
8
+ */
9
+ export async function runBrowserCapture(request) {
10
+ return captureViewport(request);
11
+ }
12
+ //# sourceMappingURL=browserCaptureService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browserCaptureService.js","sourceRoot":"","sources":["../../src/application/browserCaptureService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAKhE;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAAqC;IAC3E,OAAO,eAAe,CAAC,OAAO,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,59 @@
1
+ import type { NormalizedObservationRequest } from '../request/request.js';
2
+ import type { BrowserCaptureResult } from '../browser/types.js';
3
+ import type { ObservationArtifact } from '../domain/schema.js';
4
+ import type { CompletionState } from '../domain/completion.js';
5
+ import type { Diagnostic } from '../domain/diagnostics.js';
6
+ import type { PersistedObservationResult } from '../artifacts/types.js';
7
+ import type { WriteObservationArtifactOptions } from '../artifacts/artifactWriter.js';
8
+ export type { PersistedObservationResult } from '../artifacts/types.js';
9
+ /**
10
+ * Assembles the frozen ObservationArtifact shape from one already-successful
11
+ * Batch 2/3 browser capture. Pure - no filesystem access, no Playwright
12
+ * dependency - so it is testable independently of both Chromium and disk
13
+ * I/O. `requestId`/`observationId` reuse the existing Batch 1 identity
14
+ * functions verbatim; `completion` reuses the existing Batch 1
15
+ * post-capture completion rule over the capture's own diagnostics.
16
+ */
17
+ export declare function buildObservationArtifact(capture: Extract<BrowserCaptureResult, {
18
+ ok: true;
19
+ }>, request: NormalizedObservationRequest): ObservationArtifact;
20
+ /**
21
+ * Minimum application-facing persistence seam for Batch 5: combines an
22
+ * existing successful browser capture with the artifact writer. Not wired to
23
+ * the CLI in this batch.
24
+ */
25
+ export declare function persistBrowserCapture(capture: Extract<BrowserCaptureResult, {
26
+ ok: true;
27
+ }>, request: NormalizedObservationRequest, options?: WriteObservationArtifactOptions): Promise<PersistedObservationResult>;
28
+ /**
29
+ * Observer-owned summary of one full observation attempt, with just enough
30
+ * information for a presentation layer (the Batch 5 CLI) to report a result
31
+ * without ever seeing a Playwright object, a raw Node filesystem exception,
32
+ * or persistence implementation detail.
33
+ */
34
+ export type ApplicationObservationResult = {
35
+ ok: true;
36
+ observationId: string;
37
+ requestId: string;
38
+ completion: CompletionState;
39
+ artifactRoot: string;
40
+ manifestPath: string;
41
+ screenshotPath: string;
42
+ targetCount: number;
43
+ diagnostics: Diagnostic[];
44
+ } | {
45
+ ok: false;
46
+ diagnostics: Diagnostic[];
47
+ };
48
+ /**
49
+ * The one canonical application-level observation use case: takes an
50
+ * already-normalized request, runs the existing browser capture exactly
51
+ * once, and - only for a successful capture - persists it exactly once
52
+ * through the existing artifact writer. Reuses `runBrowserCapture`,
53
+ * `buildObservationArtifact`, and `writeObservationArtifact` verbatim; it
54
+ * does not go through `persistBrowserCapture` because that helper re-derives
55
+ * a fresh `ObservationArtifact` (and therefore a fresh, different
56
+ * `observationId`) internally, which would desync the identity a caller
57
+ * inspects here from the identity actually written to disk.
58
+ */
59
+ export declare function observe(request: NormalizedObservationRequest, options?: WriteObservationArtifactOptions): Promise<ApplicationObservationResult>;
@@ -0,0 +1,78 @@
1
+ import { runBrowserCapture } from './browserCaptureService.js';
2
+ import { ARTIFACT_KIND, SCHEMA_VERSION, getProducerInfo } from '../domain/schema.js';
3
+ import { buildRequestIdentity, buildObservationIdentity } from '../domain/identity.js';
4
+ import { deriveCompletion } from '../domain/completion.js';
5
+ import { writeObservationArtifact, SCREENSHOT_FILENAME } from '../artifacts/artifactWriter.js';
6
+ /**
7
+ * Assembles the frozen ObservationArtifact shape from one already-successful
8
+ * Batch 2/3 browser capture. Pure - no filesystem access, no Playwright
9
+ * dependency - so it is testable independently of both Chromium and disk
10
+ * I/O. `requestId`/`observationId` reuse the existing Batch 1 identity
11
+ * functions verbatim; `completion` reuses the existing Batch 1
12
+ * post-capture completion rule over the capture's own diagnostics.
13
+ */
14
+ export function buildObservationArtifact(capture, request) {
15
+ const requestId = buildRequestIdentity(request);
16
+ const observationId = buildObservationIdentity(requestId);
17
+ const completion = deriveCompletion(capture.diagnostics, 'post-capture');
18
+ return {
19
+ artifactKind: ARTIFACT_KIND,
20
+ schemaVersion: SCHEMA_VERSION,
21
+ observationId,
22
+ requestId,
23
+ producer: getProducerInfo(),
24
+ browser: { state: 'available', source: 'browser', value: capture.provenance },
25
+ requestConfig: request,
26
+ provenance: { capturedAt: new Date().toISOString(), observationMethod: 'chromium-navigate-and-capture' },
27
+ pageEvidence: capture.pageEvidence,
28
+ targetEvidence: capture.targetEvidence,
29
+ screenshot: { state: 'available', source: 'browser', value: { path: SCREENSHOT_FILENAME } },
30
+ completion,
31
+ diagnostics: capture.diagnostics,
32
+ limits: { truncated: false, omittedFields: [], omittedTargets: [] },
33
+ artifactReferences: [{ path: SCREENSHOT_FILENAME, kind: 'screenshot' }],
34
+ };
35
+ }
36
+ /**
37
+ * Minimum application-facing persistence seam for Batch 5: combines an
38
+ * existing successful browser capture with the artifact writer. Not wired to
39
+ * the CLI in this batch.
40
+ */
41
+ export async function persistBrowserCapture(capture, request, options = {}) {
42
+ const artifact = buildObservationArtifact(capture, request);
43
+ return writeObservationArtifact(artifact, capture.screenshot, options);
44
+ }
45
+ /**
46
+ * The one canonical application-level observation use case: takes an
47
+ * already-normalized request, runs the existing browser capture exactly
48
+ * once, and - only for a successful capture - persists it exactly once
49
+ * through the existing artifact writer. Reuses `runBrowserCapture`,
50
+ * `buildObservationArtifact`, and `writeObservationArtifact` verbatim; it
51
+ * does not go through `persistBrowserCapture` because that helper re-derives
52
+ * a fresh `ObservationArtifact` (and therefore a fresh, different
53
+ * `observationId`) internally, which would desync the identity a caller
54
+ * inspects here from the identity actually written to disk.
55
+ */
56
+ export async function observe(request, options = {}) {
57
+ const capture = await runBrowserCapture(request);
58
+ if (!capture.ok) {
59
+ return { ok: false, diagnostics: capture.diagnostics };
60
+ }
61
+ const artifact = buildObservationArtifact(capture, request);
62
+ const persisted = await writeObservationArtifact(artifact, capture.screenshot, options);
63
+ if (!persisted.ok) {
64
+ return { ok: false, diagnostics: persisted.diagnostics };
65
+ }
66
+ return {
67
+ ok: true,
68
+ observationId: artifact.observationId,
69
+ requestId: artifact.requestId,
70
+ completion: artifact.completion,
71
+ artifactRoot: persisted.artifactRoot,
72
+ manifestPath: persisted.manifestPath,
73
+ screenshotPath: persisted.screenshotPath,
74
+ targetCount: Object.keys(artifact.targetEvidence).length,
75
+ diagnostics: artifact.diagnostics,
76
+ };
77
+ }
78
+ //# sourceMappingURL=observationPersistence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observationPersistence.js","sourceRoot":"","sources":["../../src/application/observationPersistence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { ObservationArtifact } from '../domain/schema.js';
2
+ import type { PersistedObservationResult } from './types.js';
3
+ export declare const MANIFEST_FILENAME = "manifest.json";
4
+ export declare const SCREENSHOT_FILENAME = "screenshot.png";
5
+ export interface WriteObservationArtifactOptions {
6
+ /** Base directory that `artifact.requestConfig.outputLocation` is resolved against. Defaults to `process.cwd()`. */
7
+ cwd?: string;
8
+ }
9
+ /**
10
+ * Persists one already-assembled, already-validated ObservationArtifact plus
11
+ * its screenshot bytes as one portable artifact directory:
12
+ * `<outputLocation>/<observationId>/{manifest.json,screenshot.png}`.
13
+ *
14
+ * `observationId` is guaranteed fresh per capture (see
15
+ * domain/identity.ts#buildObservationIdentity's "never collides" contract),
16
+ * so each write targets its own unique subdirectory - no destructive
17
+ * overwrite decision is needed for the normal case; an existing directory at
18
+ * that path is treated as a genuine collision and rejected rather than
19
+ * overwritten. Writing happens in a sibling temporary directory first,
20
+ * `screenshot.png` before `manifest.json` (manifest-last), and the whole
21
+ * temporary directory is only exposed under its real name via one atomic
22
+ * rename - a consumer can never observe a partially-written artifact under
23
+ * its final name.
24
+ */
25
+ export declare function writeObservationArtifact(artifact: ObservationArtifact, screenshotBytes: Uint8Array, options?: WriteObservationArtifactOptions): Promise<PersistedObservationResult>;
@@ -0,0 +1,68 @@
1
+ import { mkdir, writeFile, rename, rm, access } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { isValidObservationArtifact } from '../domain/schema.js';
4
+ import { DIAGNOSTIC_SEVERITY } from '../domain/diagnostics.js';
5
+ export const MANIFEST_FILENAME = 'manifest.json';
6
+ export const SCREENSHOT_FILENAME = 'screenshot.png';
7
+ function writeFailure(message, details) {
8
+ const diagnostic = {
9
+ code: 'artifact-write-failure',
10
+ severity: DIAGNOSTIC_SEVERITY['artifact-write-failure'],
11
+ message,
12
+ ...(details === undefined ? {} : { details }),
13
+ };
14
+ return { ok: false, diagnostics: [diagnostic] };
15
+ }
16
+ async function pathExists(candidate) {
17
+ return access(candidate).then(() => true, () => false);
18
+ }
19
+ /**
20
+ * Persists one already-assembled, already-validated ObservationArtifact plus
21
+ * its screenshot bytes as one portable artifact directory:
22
+ * `<outputLocation>/<observationId>/{manifest.json,screenshot.png}`.
23
+ *
24
+ * `observationId` is guaranteed fresh per capture (see
25
+ * domain/identity.ts#buildObservationIdentity's "never collides" contract),
26
+ * so each write targets its own unique subdirectory - no destructive
27
+ * overwrite decision is needed for the normal case; an existing directory at
28
+ * that path is treated as a genuine collision and rejected rather than
29
+ * overwritten. Writing happens in a sibling temporary directory first,
30
+ * `screenshot.png` before `manifest.json` (manifest-last), and the whole
31
+ * temporary directory is only exposed under its real name via one atomic
32
+ * rename - a consumer can never observe a partially-written artifact under
33
+ * its final name.
34
+ */
35
+ export async function writeObservationArtifact(artifact, screenshotBytes, options = {}) {
36
+ const validation = isValidObservationArtifact(artifact);
37
+ if (!validation.valid) {
38
+ return writeFailure(`refusing to persist an invalid ObservationArtifact: ${validation.reason}`);
39
+ }
40
+ const cwd = options.cwd ?? process.cwd();
41
+ const outputRoot = path.resolve(cwd, artifact.requestConfig.outputLocation);
42
+ const finalRoot = path.join(outputRoot, artifact.observationId);
43
+ const tempRoot = path.join(outputRoot, `.tmp-${artifact.observationId}`);
44
+ if (await pathExists(finalRoot)) {
45
+ return writeFailure(`an observation artifact already exists at "${artifact.observationId}"; refusing to overwrite it`, {
46
+ artifactRoot: finalRoot,
47
+ });
48
+ }
49
+ try {
50
+ await rm(tempRoot, { recursive: true, force: true });
51
+ await mkdir(tempRoot, { recursive: true });
52
+ await writeFile(path.join(tempRoot, SCREENSHOT_FILENAME), screenshotBytes);
53
+ await writeFile(path.join(tempRoot, MANIFEST_FILENAME), JSON.stringify(artifact, null, 2), 'utf8');
54
+ await rename(tempRoot, finalRoot);
55
+ return {
56
+ ok: true,
57
+ artifactRoot: finalRoot,
58
+ manifestPath: path.join(finalRoot, MANIFEST_FILENAME),
59
+ screenshotPath: path.join(finalRoot, SCREENSHOT_FILENAME),
60
+ };
61
+ }
62
+ catch (err) {
63
+ await rm(tempRoot, { recursive: true, force: true }).catch(() => undefined);
64
+ const message = err instanceof Error ? err.message : String(err);
65
+ return writeFailure(`failed to persist observation artifact: ${message}`);
66
+ }
67
+ }
68
+ //# sourceMappingURL=artifactWriter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifactWriter.js","sourceRoot":"","sources":["../../src/artifacts/artifactWriter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { Diagnostic } from '../domain/diagnostics.js';
2
+ /**
3
+ * Observer-owned result of persisting one ObservationArtifact. `artifactRoot`
4
+ * and the file paths are absolute filesystem paths for the *caller's* local
5
+ * use only (e.g. to print or open the result); they are never written back
6
+ * into the persisted manifest itself, which stores only portable relative
7
+ * references.
8
+ */
9
+ export type PersistedObservationResult = {
10
+ ok: true;
11
+ artifactRoot: string;
12
+ manifestPath: string;
13
+ screenshotPath: string;
14
+ } | {
15
+ ok: false;
16
+ diagnostics: Diagnostic[];
17
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/artifacts/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,21 @@
1
+ import type { NormalizedObservationRequest } from '../request/request.js';
2
+ import type { BrowserCaptureResult } from './types.js';
3
+ export type { BrowserCaptureResult, BrowserProvenance } from './types.js';
4
+ export interface CaptureViewportInternalResult {
5
+ result: BrowserCaptureResult;
6
+ /** True if the launched browser process is still connected after cleanup ran. Always false unless cleanup failed. */
7
+ browserConnected: boolean;
8
+ }
9
+ /**
10
+ * Owns the entire launch -> context -> page -> navigate -> ready -> screenshot
11
+ * -> close lifecycle for one capture. Guarantees `browser.close()` runs on
12
+ * every exit path (success, safety rejection, navigation/readiness failure,
13
+ * or an unexpected internal error), so callers never manage cleanup
14
+ * themselves. Exported (not re-exported from src/index.ts or the application
15
+ * seam) only so integration tests can assert `browserConnected` becomes
16
+ * false after every call - the Browser handle itself never leaves this
17
+ * module.
18
+ */
19
+ export declare function captureViewportInternal(request: NormalizedObservationRequest): Promise<CaptureViewportInternalResult>;
20
+ /** Observer-facing entrypoint: no Playwright object is reachable from its signature or return value. */
21
+ export declare function captureViewport(request: NormalizedObservationRequest): Promise<BrowserCaptureResult>;
@@ -0,0 +1,150 @@
1
+ import { chromium } from 'playwright';
2
+ import { DIAGNOSTIC_SEVERITY } from '../domain/diagnostics.js';
3
+ import { classifyUrl, classifyRedirect, classifySubresource, classifyPopup, classifyDownload } from '../safety/policy.js';
4
+ import { capturePageEvidence, captureTargetEvidence } from './evidenceCapture.js';
5
+ function diagnostic(code, message) {
6
+ return { code, severity: DIAGNOSTIC_SEVERITY[code], message };
7
+ }
8
+ function classifyNavigationError(error, readinessTimeoutMs) {
9
+ const message = error instanceof Error ? error.message : String(error);
10
+ if (/timeout/i.test(message)) {
11
+ return diagnostic('readiness-timeout', `readiness condition not reached within ${readinessTimeoutMs}ms: ${message}`);
12
+ }
13
+ return diagnostic('navigation-failure', `navigation failed: ${message}`);
14
+ }
15
+ /**
16
+ * Owns the entire launch -> context -> page -> navigate -> ready -> screenshot
17
+ * -> close lifecycle for one capture. Guarantees `browser.close()` runs on
18
+ * every exit path (success, safety rejection, navigation/readiness failure,
19
+ * or an unexpected internal error), so callers never manage cleanup
20
+ * themselves. Exported (not re-exported from src/index.ts or the application
21
+ * seam) only so integration tests can assert `browserConnected` becomes
22
+ * false after every call - the Browser handle itself never leaves this
23
+ * module.
24
+ */
25
+ export async function captureViewportInternal(request) {
26
+ const initialDecision = classifyUrl(request.targetUrl);
27
+ if (!initialDecision.allowed) {
28
+ return { result: { ok: false, diagnostics: [initialDecision.diagnostic] }, browserConnected: false };
29
+ }
30
+ let browser;
31
+ let result;
32
+ try {
33
+ browser = await chromium.launch({ headless: true });
34
+ const context = await browser.newContext({
35
+ viewport: { width: request.viewport.width, height: request.viewport.height },
36
+ });
37
+ const page = await context.newPage();
38
+ const diagnostics = [];
39
+ page.on('popup', () => diagnostics.push(classifyPopup()));
40
+ page.on('download', () => diagnostics.push(classifyDownload()));
41
+ let lastAllowedMainFrameUrl = request.targetUrl;
42
+ let rejection;
43
+ await page.route('**/*', async (route) => {
44
+ const req = route.request();
45
+ const url = req.url();
46
+ const isMainFrameNavigation = req.isNavigationRequest() && req.frame() === page.mainFrame();
47
+ if (!isMainFrameNavigation) {
48
+ const decision = classifySubresource(url);
49
+ if (!decision.allowed) {
50
+ rejection = rejection ?? decision.diagnostic;
51
+ await route.abort('blockedbyclient').catch(() => undefined);
52
+ return;
53
+ }
54
+ await route.continue().catch(() => undefined);
55
+ return;
56
+ }
57
+ // Chromium follows navigation redirects internally without re-emitting a
58
+ // routable request per hop, so each main-frame hop is fetched manually
59
+ // (maxRedirects: 0) and inspected here before any further hop is
60
+ // allowed to proceed - this is what lets a prohibited redirect target
61
+ // be blocked before it is ever contacted.
62
+ const decision = url === lastAllowedMainFrameUrl ? { allowed: true } : classifyRedirect(lastAllowedMainFrameUrl, url);
63
+ if (!decision.allowed) {
64
+ rejection = rejection ?? decision.diagnostic;
65
+ await route.fulfill({ status: 204, body: '' }).catch(() => undefined);
66
+ return;
67
+ }
68
+ lastAllowedMainFrameUrl = url;
69
+ let response;
70
+ try {
71
+ response = await route.fetch({ maxRedirects: 0 });
72
+ }
73
+ catch {
74
+ await route.abort('failed').catch(() => undefined);
75
+ return;
76
+ }
77
+ const status = response.status();
78
+ if (status >= 300 && status < 400) {
79
+ const location = response.headers()['location'];
80
+ const resolvedUrl = location ? new URL(location, url).toString() : undefined;
81
+ const redirectDecision = resolvedUrl ? classifyRedirect(lastAllowedMainFrameUrl, resolvedUrl) : { allowed: true };
82
+ if (!redirectDecision.allowed) {
83
+ rejection = rejection ?? redirectDecision.diagnostic;
84
+ await route.fulfill({ status: 204, body: '' }).catch(() => undefined);
85
+ return;
86
+ }
87
+ }
88
+ await route.fulfill({ response }).catch(() => undefined);
89
+ });
90
+ let navigationError;
91
+ try {
92
+ await page.goto(request.targetUrl, {
93
+ waitUntil: request.readiness.condition,
94
+ timeout: request.readiness.timeoutMs,
95
+ });
96
+ }
97
+ catch (err) {
98
+ navigationError = err;
99
+ }
100
+ if (rejection) {
101
+ result = { ok: false, diagnostics: [rejection, ...diagnostics] };
102
+ }
103
+ else if (navigationError) {
104
+ result = { ok: false, diagnostics: [...diagnostics, classifyNavigationError(navigationError, request.readiness.timeoutMs)] };
105
+ }
106
+ else {
107
+ // Captured from the same live page, after the same readiness point, as
108
+ // required by Batch 3: no second browser/page is ever opened.
109
+ const screenshotBuffer = await page.screenshot({ type: 'png' });
110
+ const provenance = { engine: 'chromium', version: browser.version() };
111
+ let pageEvidence;
112
+ try {
113
+ pageEvidence = await capturePageEvidence(page, request.targetUrl, request.viewport);
114
+ }
115
+ catch (err) {
116
+ const message = err instanceof Error ? err.message : String(err);
117
+ diagnostics.push(diagnostic('browser-evidence-unavailable', `page evidence collection failed: ${message}`));
118
+ pageEvidence = {};
119
+ }
120
+ let targetEvidence;
121
+ try {
122
+ const captured = await captureTargetEvidence(page, request.targets);
123
+ targetEvidence = captured.targetEvidence;
124
+ diagnostics.push(...captured.diagnostics);
125
+ }
126
+ catch (err) {
127
+ const message = err instanceof Error ? err.message : String(err);
128
+ diagnostics.push(diagnostic('browser-evidence-unavailable', `target evidence collection failed: ${message}`));
129
+ targetEvidence = {};
130
+ }
131
+ result = { ok: true, provenance, screenshot: new Uint8Array(screenshotBuffer), pageEvidence, targetEvidence, diagnostics };
132
+ }
133
+ }
134
+ catch (err) {
135
+ const message = err instanceof Error ? err.message : String(err);
136
+ result = { ok: false, diagnostics: [diagnostic('browser-runtime-failure', `unexpected browser failure: ${message}`)] };
137
+ }
138
+ finally {
139
+ if (browser) {
140
+ await browser.close().catch(() => undefined);
141
+ }
142
+ }
143
+ return { result, browserConnected: browser ? browser.isConnected() : false };
144
+ }
145
+ /** Observer-facing entrypoint: no Playwright object is reachable from its signature or return value. */
146
+ export async function captureViewport(request) {
147
+ const { result } = await captureViewportInternal(request);
148
+ return result;
149
+ }
150
+ //# sourceMappingURL=chromiumAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chromiumAdapter.js","sourceRoot":"","sources":["../../src/browser/chromiumAdapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { Page } from 'playwright';
2
+ import type { EvidenceField } from '../domain/evidence.js';
3
+ import type { Diagnostic } from '../domain/diagnostics.js';
4
+ import type { NamedTarget, Viewport } from '../request/request.js';
5
+ import type { TargetEvidenceRecord } from '../domain/schema.js';
6
+ /**
7
+ * Captures the v0.1 minimum page evidence (docs/PROJECT_MILESTONES.md
8
+ * "Minimum page evidence") from the already-navigated, already-ready page.
9
+ * `documentWidth`/`documentHeight` are the one explicitly *derived* pair -
10
+ * max(scroll, client) in each axis - everything else is a direct browser
11
+ * read, per the Batch 1 evidence-source model.
12
+ */
13
+ export declare function capturePageEvidence(page: Page, requestedUrl: string, viewport: Viewport): Promise<Record<string, EvidenceField<unknown>>>;
14
+ export interface TargetEvidenceCaptureResult {
15
+ targetEvidence: Record<string, TargetEvidenceRecord>;
16
+ diagnostics: Diagnostic[];
17
+ }
18
+ /** Observes every explicitly configured Batch 1 target from the same live page, honoring the 0/1/many cardinality contract. */
19
+ export declare function captureTargetEvidence(page: Page, targets: readonly NamedTarget[]): Promise<TargetEvidenceCaptureResult>;