my-frontend-observer 0.4.0 → 0.5.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 (53) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +39 -11
  3. package/dist/application/frontendContractEvaluationService.d.ts +49 -0
  4. package/dist/application/frontendContractEvaluationService.js +112 -0
  5. package/dist/application/frontendContractEvaluationService.js.map +1 -0
  6. package/dist/application/frontendContractPersistenceService.d.ts +56 -0
  7. package/dist/application/frontendContractPersistenceService.js +91 -0
  8. package/dist/application/frontendContractPersistenceService.js.map +1 -0
  9. package/dist/artifacts/comparisonArtifactReader.d.ts +18 -0
  10. package/dist/artifacts/comparisonArtifactReader.js +35 -0
  11. package/dist/artifacts/comparisonArtifactReader.js.map +1 -0
  12. package/dist/artifacts/frontendContractArtifactReader.d.ts +24 -0
  13. package/dist/artifacts/frontendContractArtifactReader.js +47 -0
  14. package/dist/artifacts/frontendContractArtifactReader.js.map +1 -0
  15. package/dist/artifacts/frontendContractArtifactWriter.d.ts +34 -0
  16. package/dist/artifacts/frontendContractArtifactWriter.js +70 -0
  17. package/dist/artifacts/frontendContractArtifactWriter.js.map +1 -0
  18. package/dist/artifacts/frontendContractEvaluationArtifactReader.d.ts +17 -0
  19. package/dist/artifacts/frontendContractEvaluationArtifactReader.js +34 -0
  20. package/dist/artifacts/frontendContractEvaluationArtifactReader.js.map +1 -0
  21. package/dist/artifacts/frontendContractEvaluationArtifactWriter.d.ts +32 -0
  22. package/dist/artifacts/frontendContractEvaluationArtifactWriter.js +58 -0
  23. package/dist/artifacts/frontendContractEvaluationArtifactWriter.js.map +1 -0
  24. package/dist/cli.js +496 -6
  25. package/dist/cli.js.map +1 -1
  26. package/dist/domain/frontendContractEvaluation.d.ts +57 -0
  27. package/dist/domain/frontendContractEvaluation.js +454 -0
  28. package/dist/domain/frontendContractEvaluation.js.map +1 -0
  29. package/dist/domain/frontendContractEvaluationArtifact.d.ts +65 -0
  30. package/dist/domain/frontendContractEvaluationArtifact.js +108 -0
  31. package/dist/domain/frontendContractEvaluationArtifact.js.map +1 -0
  32. package/dist/domain/frontendContractIdentity.d.ts +39 -0
  33. package/dist/domain/frontendContractIdentity.js +70 -0
  34. package/dist/domain/frontendContractIdentity.js.map +1 -0
  35. package/dist/domain/frontendContracts.d.ts +188 -0
  36. package/dist/domain/frontendContracts.js +260 -0
  37. package/dist/domain/frontendContracts.js.map +1 -0
  38. package/dist/index.d.ts +22 -0
  39. package/dist/index.js +12 -0
  40. package/dist/index.js.map +1 -1
  41. package/docs/ARCHITECTURE.md +98 -1
  42. package/docs/CI_CD.md +47 -0
  43. package/docs/COMMANDS.md +162 -0
  44. package/docs/CONTRACTS.md +175 -3
  45. package/docs/CURRENT_STATE.md +92 -10
  46. package/docs/DEVELOPMENT.md +58 -10
  47. package/docs/PROJECT_OVERVIEW.md +19 -13
  48. package/docs/QUICKSTART.md +5 -0
  49. package/docs/RELEASE.md +17 -10
  50. package/docs/ROADMAP.md +6 -0
  51. package/docs/SECURITY.md +13 -1
  52. package/docs/WORKFLOWS.md +65 -8
  53. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,65 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0 - 2026-08-13
4
+
5
+ Executable Frontend Contracts and Explicit Change Scope.
6
+
7
+ - Two related contract classes: a `PersistentBaselineContract` (previously
8
+ approved frontend behavior that stays active across future changes unless
9
+ explicitly superseded, with append-based supersession history) and a
10
+ `PerChangeContract` (the allowed scope of one requested change).
11
+ - Four authored change-scope categories - `requested`, `expected-dependent`
12
+ (`required` or `permitted`), `protected`, `preserved` - plus a fifth,
13
+ strictly derived-only classification, `unexpected`, for a meaningful
14
+ rendered difference no active clause accounts for. `unexpected` can never
15
+ be authored as a permission.
16
+ - A closed, bounded vocabulary of 15 contract primitives (visibility,
17
+ clipping, width bounds, non-overlap, relative width, vertical sequence,
18
+ geometric fit, document-width-vs-viewport, scroll ownership, initial-
19
+ viewport position, relationship-unchanged, and property-unchanged/
20
+ increases/decreases) and three contract tolerances (`exact`,
21
+ `absolute-px`, `percent`) - independent of `compare`'s geometry tolerance,
22
+ which only suppresses insignificant noise and is never contract
23
+ authorization.
24
+ - Explicit, never-inferred baseline and per-change clause supersession; two
25
+ clauses that structurally contradict each other without explicit
26
+ supersession produce a `conflict` result rather than a silent preference.
27
+ - One canonical evaluation engine (`evaluateFrontendContract`) that owns
28
+ requested/expected-dependent/protected/preserved evaluation, unexpected-
29
+ change derivation, and the overall `PASS`/`FAIL` verdict - reusing existing
30
+ v0.4 observation/comparison evidence directly, never re-launching a
31
+ browser, re-resolving a target, or reimplementing relationship/clipping
32
+ derivation.
33
+ - Actionable per-clause results (`pass`/`fail`/`unavailable` with a required
34
+ reason/`conflict` with at least two conflicting clause identities) - never
35
+ an opaque score.
36
+ - Atomic, independently-versioned persistence for baseline contracts,
37
+ per-change contracts, and evaluation results, with no destructive artifact
38
+ overwrite, no copied screenshots, and full source observation/comparison/
39
+ contract immutability.
40
+ - Three new public commands: `approve-baseline` (the only baseline-approval
41
+ act - explicit only, never inferred from `compare` or a `PASS`
42
+ evaluation), `save-change-contract` (persistence only), and
43
+ `evaluate-contract` (runs the canonical evaluator against already-
44
+ persisted evidence and persists exactly one evaluation artifact).
45
+ `evaluate-contract --enforce` makes an already-persisted `FAIL` verdict
46
+ produce a nonzero process exit status without changing the verdict, its
47
+ identity, or its persisted content - a `FAIL` without `--enforce` still
48
+ exits `0`.
49
+ - Proven against real Chromium observations, not hand-constructed
50
+ artifacts: a fully successful contract change, and the "milestone
51
+ signature" case - a locally successful requested change coexisting with a
52
+ genuine protected-property regression and a genuine preserved-invariant
53
+ regression - producing overall `FAIL`.
54
+ - Frontend contract schema `1.0.0` and evaluation artifact schema `1.0.0`,
55
+ each its own independent schema family; observation schema remains
56
+ `1.2.0` and comparison schema remains `1.0.0`.
57
+ - Cross-platform packed-candidate validation: one hash-verified npm
58
+ candidate tarball proven on Windows, Linux, and macOS, covering the
59
+ installed candidate's `approve-baseline`, `save-change-contract`, and
60
+ `evaluate-contract` commands alongside every pre-existing v0.1-v0.4
61
+ packed observation/comparison behavior.
62
+
3
63
  ## 0.4.0 - 2026-08-12
4
64
 
5
65
  Layout Relationships, Dependency Evidence, and Before/After Comparison.
package/README.md CHANGED
@@ -6,15 +6,21 @@ in [docs/PROJECT_DESCRIPTION.md](docs/PROJECT_DESCRIPTION.md).
6
6
 
7
7
  ## Current status
8
8
 
9
- `v0.4.0`, Layout Relationships, Dependency Evidence, and Before/After
10
- Comparison, is the current published release: `my-frontend-observer observe`
11
- launches a real, sandboxed Chromium browser, enforces a loopback-only safety
12
- policy, captures a viewport screenshot plus bounded page/target evidence,
13
- and persists it as one portable `manifest.json` + `screenshot.png` artifact
14
- (observation schema `1.2.0`). `my-frontend-observer compare` reads two
15
- already-persisted observation artifacts and derives before/after evidence
16
- purely from their existing content, persisting a comparison artifact
17
- (comparison schema `1.0.0`).
9
+ `v0.5.0`, Executable Frontend Contracts and Explicit Change Scope, is the
10
+ current published release: `my-frontend-observer observe` launches a real,
11
+ sandboxed Chromium browser, enforces a loopback-only safety policy, captures
12
+ a viewport screenshot plus bounded page/target evidence, and persists it as
13
+ one portable `manifest.json` + `screenshot.png` artifact (observation schema
14
+ `1.2.0`). `my-frontend-observer compare` reads two already-persisted
15
+ observation artifacts and derives before/after evidence purely from their
16
+ existing content, persisting a comparison artifact (comparison schema
17
+ `1.0.0`). `my-frontend-observer approve-baseline`, `save-change-contract`,
18
+ and `evaluate-contract` turn that evidence into an executable frontend
19
+ contract: an explicitly approved baseline plus a per-change contract
20
+ (requested/expected-dependent/protected/preserved scope) are evaluated
21
+ together into one `PASS`/`FAIL` verdict, so a locally successful requested
22
+ change can never silently hide a protected-region regression (frontend
23
+ contract schema `1.0.0`; evaluation artifact schema `1.0.0`).
18
24
 
19
25
  Install:
20
26
 
@@ -91,6 +97,28 @@ including when the two observations turn out to be `incomparable`, which is
91
97
  itself a successful comparison outcome. See
92
98
  [docs/COMMANDS.md](docs/COMMANDS.md) for the full flag reference.
93
99
 
100
+ ### Frontend contracts
101
+
102
+ `v0.5.0` ships a text/config-driven frontend contract and evaluation
103
+ workflow: approve a baseline against an observation, save a per-change
104
+ contract, then evaluate a candidate change against them plus existing
105
+ before/after/comparison evidence, deriving one `PASS`/`FAIL` verdict:
106
+
107
+ ```powershell
108
+ my-frontend-observer approve-baseline --observation observations/<id> --contract-file baseline.json --output baselines
109
+ my-frontend-observer save-change-contract --contract-file change.json --output contracts
110
+ my-frontend-observer evaluate-contract --before observations/<before-id> --after observations/<after-id> --comparison comparisons/<id> --baseline baselines/<baseline-id> --change contracts/<contract-id> --output evaluations [--enforce]
111
+ ```
112
+
113
+ (From a source checkout, use `node dist/cli.js approve-baseline ...` etc.
114
+ instead.)
115
+
116
+ `evaluate-contract` never launches a browser or recomputes comparison
117
+ evidence. `--enforce` only changes the process exit status for a `FAIL`
118
+ verdict; the verdict itself, and its persisted evidence, are unaffected. See
119
+ [docs/COMMANDS.md](docs/COMMANDS.md) for the full flag reference and
120
+ [docs/WORKFLOWS.md](docs/WORKFLOWS.md) for the end-to-end flow.
121
+
94
122
  Validation:
95
123
 
96
124
  ```powershell
@@ -108,8 +136,8 @@ Planning authorities:
108
136
  intent and responsibility boundaries.
109
137
  - [Project Milestones](docs/PROJECT_MILESTONES.md): complete ordered capability
110
138
  design and cross-milestone rules.
111
- - [ROADMAP](docs/ROADMAP.md): version-level requirements; v0.1-v0.4 are
112
- released, v0.5 is next.
139
+ - [ROADMAP](docs/ROADMAP.md): version-level requirements; v0.1-v0.5 are
140
+ released, v0.6 is next.
113
141
  - [Current State](docs/CURRENT_STATE.md): retained scaffold and release state.
114
142
 
115
143
  No sibling ecosystem repository is a runtime dependency of the retained
@@ -0,0 +1,49 @@
1
+ import type { FrontendContractEvaluationInput } from '../domain/frontendContractEvaluation.js';
2
+ import type { OverallVerdict } from '../domain/frontendContracts.js';
3
+ import type { Diagnostic } from '../domain/diagnostics.js';
4
+ import type { WriteFrontendContractEvaluationArtifactOptions } from '../artifacts/frontendContractEvaluationArtifactWriter.js';
5
+ export type { PersistedFrontendContractEvaluationResult } from '../artifacts/frontendContractEvaluationArtifactWriter.js';
6
+ /** Distinct from `DEFAULT_COMPARISON_OUTPUT_LOCATION`/observation `outputLocation` so evaluations never collide with source evidence. */
7
+ export declare const DEFAULT_EVALUATION_OUTPUT_LOCATION = "evaluations";
8
+ export interface EvaluateAndPersistOptions {
9
+ outputLocation?: string;
10
+ cwd?: WriteFrontendContractEvaluationArtifactOptions['cwd'];
11
+ }
12
+ export type ApplicationFrontendContractEvaluationResult = {
13
+ ok: true;
14
+ evaluationId: string;
15
+ evaluationRequestId: string;
16
+ overallVerdict: OverallVerdict;
17
+ artifactRoot: string;
18
+ manifestPath: string;
19
+ clauseResultCount: number;
20
+ unexpectedChangeCount: number;
21
+ } | {
22
+ ok: false;
23
+ diagnostics: Diagnostic[];
24
+ };
25
+ /**
26
+ * The one canonical application-level frontend-contract evaluation use case:
27
+ * takes already-validated/in-memory evidence, runs the existing pure
28
+ * `evaluateFrontendContract` exactly once, and - only for a structurally
29
+ * constructible result (`ok: true`, whether the verdict itself is `PASS` or
30
+ * `FAIL`) - persists it exactly once through the existing evaluation writer.
31
+ * Never re-evaluates, never launches Chromium, never mutates any input
32
+ * artifact. A `{ ok: false }` evaluator result (evidence could not even be
33
+ * constructed into an evaluation) is never persisted as a fabricated
34
+ * successful artifact - that is an application-level `invalid-request`
35
+ * diagnostic, distinct from a legitimate `FAIL` verdict.
36
+ */
37
+ export declare function evaluateAndPersist(input: FrontendContractEvaluationInput, options?: EvaluateAndPersistOptions): Promise<ApplicationFrontendContractEvaluationResult>;
38
+ /**
39
+ * The canonical future-CLI-facing orchestration: reads before/after
40
+ * observations (through the existing `readObservationArtifact` reader -
41
+ * never a second observation reader), the comparison (through the new
42
+ * `readComparisonArtifact`), and the baseline/per-change contracts (through
43
+ * `frontendContractArtifactReader.ts`) by their root directories, then
44
+ * delegates to `evaluateAndPersist` exactly once. A thin wrapper only -
45
+ * reading stays owned by the artifacts layer, evaluation stays owned by
46
+ * `domain/frontendContractEvaluation.ts`, persistence stays owned by
47
+ * `artifacts/frontendContractEvaluationArtifactWriter.ts`.
48
+ */
49
+ export declare function evaluateAndPersistFromArtifactRoots(beforeRoot: string, afterRoot: string, comparisonRoot: string, baselineRoot: string, changeRoot: string, options?: EvaluateAndPersistOptions): Promise<ApplicationFrontendContractEvaluationResult>;
@@ -0,0 +1,112 @@
1
+ import path from 'node:path';
2
+ import { getProducerInfo } from '../domain/schema.js';
3
+ import { evaluateFrontendContract } from '../domain/frontendContractEvaluation.js';
4
+ import { buildFrontendContractEvaluationArtifact } from '../domain/frontendContractEvaluationArtifact.js';
5
+ import { buildFrontendContractEvaluationRequestIdentity, buildFrontendContractInstanceIdentity } from '../domain/frontendContractIdentity.js';
6
+ import { DIAGNOSTIC_SEVERITY } from '../domain/diagnostics.js';
7
+ import { normalizeOutputLocation } from '../request/paths.js';
8
+ import { writeFrontendContractEvaluationArtifact } from '../artifacts/frontendContractEvaluationArtifactWriter.js';
9
+ import { readObservationArtifact } from '../artifacts/artifactReader.js';
10
+ import { MANIFEST_FILENAME as OBSERVATION_MANIFEST_FILENAME } from '../artifacts/artifactWriter.js';
11
+ import { readComparisonArtifact } from '../artifacts/comparisonArtifactReader.js';
12
+ import { COMPARISON_MANIFEST_FILENAME } from '../artifacts/comparisonArtifactWriter.js';
13
+ import { readPersistentBaselineContract, readPerChangeContract } from '../artifacts/frontendContractArtifactReader.js';
14
+ import { FRONTEND_CONTRACT_MANIFEST_FILENAME } from '../artifacts/frontendContractArtifactWriter.js';
15
+ /** Distinct from `DEFAULT_COMPARISON_OUTPUT_LOCATION`/observation `outputLocation` so evaluations never collide with source evidence. */
16
+ export const DEFAULT_EVALUATION_OUTPUT_LOCATION = 'evaluations';
17
+ function invalidRequest(message) {
18
+ return { ok: false, diagnostics: [{ code: 'invalid-request', severity: DIAGNOSTIC_SEVERITY['invalid-request'], message }] };
19
+ }
20
+ /**
21
+ * The one canonical application-level frontend-contract evaluation use case:
22
+ * takes already-validated/in-memory evidence, runs the existing pure
23
+ * `evaluateFrontendContract` exactly once, and - only for a structurally
24
+ * constructible result (`ok: true`, whether the verdict itself is `PASS` or
25
+ * `FAIL`) - persists it exactly once through the existing evaluation writer.
26
+ * Never re-evaluates, never launches Chromium, never mutates any input
27
+ * artifact. A `{ ok: false }` evaluator result (evidence could not even be
28
+ * constructed into an evaluation) is never persisted as a fabricated
29
+ * successful artifact - that is an application-level `invalid-request`
30
+ * diagnostic, distinct from a legitimate `FAIL` verdict.
31
+ */
32
+ export async function evaluateAndPersist(input, options = {}) {
33
+ const result = evaluateFrontendContract(input);
34
+ if (!result.ok)
35
+ return invalidRequest(result.reason);
36
+ const rawOutputLocation = options.outputLocation ?? DEFAULT_EVALUATION_OUTPUT_LOCATION;
37
+ const normalized = normalizeOutputLocation(rawOutputLocation);
38
+ if (!normalized.ok)
39
+ return { ok: false, diagnostics: [normalized.diagnostic] };
40
+ const evaluationRequestId = buildFrontendContractEvaluationRequestIdentity(input.baseline.baselineId, input.change.contractId, input.comparison.before.observationId, input.comparison.after.observationId, input.comparison.comparisonRequestId);
41
+ const evaluationId = buildFrontendContractInstanceIdentity(evaluationRequestId);
42
+ const artifact = buildFrontendContractEvaluationArtifact({
43
+ evaluationId,
44
+ evaluationRequestId,
45
+ producerVersion: getProducerInfo().version,
46
+ evaluatedAt: new Date().toISOString(),
47
+ baselineId: input.baseline.baselineId,
48
+ contractId: input.change.contractId,
49
+ before: {
50
+ observationId: input.comparison.before.observationId,
51
+ requestId: input.comparison.before.requestId,
52
+ producer: input.comparison.before.producer,
53
+ observationSchemaVersion: input.comparison.before.observationSchemaVersion,
54
+ },
55
+ after: {
56
+ observationId: input.comparison.after.observationId,
57
+ requestId: input.comparison.after.requestId,
58
+ producer: input.comparison.after.producer,
59
+ observationSchemaVersion: input.comparison.after.observationSchemaVersion,
60
+ },
61
+ comparisonId: input.comparison.comparisonId,
62
+ comparisonRequestId: input.comparison.comparisonRequestId,
63
+ overallVerdict: result.evaluation.overallVerdict,
64
+ activeBaselineClauseIds: result.evaluation.activeBaselineClauseIds,
65
+ supersededBaselineClauseIds: result.evaluation.supersededBaselineClauseIds,
66
+ clauseResults: result.evaluation.clauseResults,
67
+ unexpectedChanges: result.evaluation.unexpectedChanges,
68
+ });
69
+ const persisted = await writeFrontendContractEvaluationArtifact(artifact, normalized.value, options.cwd === undefined ? {} : { cwd: options.cwd });
70
+ if (!persisted.ok)
71
+ return { ok: false, diagnostics: persisted.diagnostics };
72
+ return {
73
+ ok: true,
74
+ evaluationId: artifact.evaluationId,
75
+ evaluationRequestId: artifact.evaluationRequestId,
76
+ overallVerdict: artifact.overallVerdict,
77
+ artifactRoot: persisted.artifactRoot,
78
+ manifestPath: persisted.manifestPath,
79
+ clauseResultCount: artifact.clauseResults.length,
80
+ unexpectedChangeCount: artifact.unexpectedChanges.length,
81
+ };
82
+ }
83
+ /**
84
+ * The canonical future-CLI-facing orchestration: reads before/after
85
+ * observations (through the existing `readObservationArtifact` reader -
86
+ * never a second observation reader), the comparison (through the new
87
+ * `readComparisonArtifact`), and the baseline/per-change contracts (through
88
+ * `frontendContractArtifactReader.ts`) by their root directories, then
89
+ * delegates to `evaluateAndPersist` exactly once. A thin wrapper only -
90
+ * reading stays owned by the artifacts layer, evaluation stays owned by
91
+ * `domain/frontendContractEvaluation.ts`, persistence stays owned by
92
+ * `artifacts/frontendContractEvaluationArtifactWriter.ts`.
93
+ */
94
+ export async function evaluateAndPersistFromArtifactRoots(beforeRoot, afterRoot, comparisonRoot, baselineRoot, changeRoot, options = {}) {
95
+ const beforeRead = await readObservationArtifact(path.join(beforeRoot, OBSERVATION_MANIFEST_FILENAME));
96
+ if (!beforeRead.ok)
97
+ return invalidRequest(`before observation artifact: ${beforeRead.reason}`);
98
+ const afterRead = await readObservationArtifact(path.join(afterRoot, OBSERVATION_MANIFEST_FILENAME));
99
+ if (!afterRead.ok)
100
+ return invalidRequest(`after observation artifact: ${afterRead.reason}`);
101
+ const comparisonRead = await readComparisonArtifact(path.join(comparisonRoot, COMPARISON_MANIFEST_FILENAME));
102
+ if (!comparisonRead.ok)
103
+ return invalidRequest(`comparison artifact: ${comparisonRead.reason}`);
104
+ const baselineRead = await readPersistentBaselineContract(path.join(baselineRoot, FRONTEND_CONTRACT_MANIFEST_FILENAME));
105
+ if (!baselineRead.ok)
106
+ return invalidRequest(`baseline contract artifact: ${baselineRead.reason}`);
107
+ const changeRead = await readPerChangeContract(path.join(changeRoot, FRONTEND_CONTRACT_MANIFEST_FILENAME));
108
+ if (!changeRead.ok)
109
+ return invalidRequest(`per-change contract artifact: ${changeRead.reason}`);
110
+ return evaluateAndPersist({ before: beforeRead.artifact, after: afterRead.artifact, comparison: comparisonRead.artifact, baseline: baselineRead.contract, change: changeRead.contract }, options);
111
+ }
112
+ //# sourceMappingURL=frontendContractEvaluationService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontendContractEvaluationService.js","sourceRoot":"","sources":["../../src/application/frontendContractEvaluationService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import type { Diagnostic } from '../domain/diagnostics.js';
2
+ import type { WriteFrontendContractArtifactOptions } from '../artifacts/frontendContractArtifactWriter.js';
3
+ export interface PersistFrontendContractOptions {
4
+ outputLocation: string;
5
+ cwd?: WriteFrontendContractArtifactOptions['cwd'];
6
+ }
7
+ export type ApplicationApproveBaselineResult = {
8
+ ok: true;
9
+ baselineId: string;
10
+ supersedesBaselineId?: string;
11
+ artifactRoot: string;
12
+ manifestPath: string;
13
+ clauseCount: number;
14
+ } | {
15
+ ok: false;
16
+ diagnostics: Diagnostic[];
17
+ };
18
+ export type ApplicationPersistChangeContractResult = {
19
+ ok: true;
20
+ contractId: string;
21
+ artifactRoot: string;
22
+ manifestPath: string;
23
+ clauseCount: number;
24
+ supersedesBaselineClauseCount: number;
25
+ } | {
26
+ ok: false;
27
+ diagnostics: Diagnostic[];
28
+ };
29
+ /**
30
+ * The one canonical application-level baseline-approval use case: `approve-baseline`
31
+ * is the *only* place a baseline is ever approved (see governing scope) - this
32
+ * function is never called from `observe`/`compare`/`evaluate-contract`, and a
33
+ * `PASS` evaluation never triggers it. Validates the raw (still-`unknown`,
34
+ * freshly-parsed-JSON) baseline through the existing frozen
35
+ * `isValidPersistentBaselineContract` structural gate (never a second
36
+ * validator), reads the supplied observation through the existing
37
+ * `readObservationArtifact` reader, verifies the baseline's frozen
38
+ * `sourceObservation` reference actually corresponds to that observation's
39
+ * stable identity, and - only then - persists exactly once through the
40
+ * existing contract writer. Never mutates the observation. Never infers,
41
+ * creates, or removes `supersedesBaselineId` - whatever the caller already
42
+ * authored is preserved exactly.
43
+ */
44
+ export declare function approveAndPersistBaseline(rawContract: unknown, observationRoot: string, options: PersistFrontendContractOptions): Promise<ApplicationApproveBaselineResult>;
45
+ /**
46
+ * The one canonical application-level per-change-contract persistence use
47
+ * case (`save-change-contract`) - persistence only, never approval
48
+ * terminology or semantics. Validates the raw contract through the existing
49
+ * frozen `isValidPerChangeContract` gate (which already rejects a
50
+ * `PersistentBaselineContract` passed here, an authored `category:
51
+ * "unexpected"`, and every other structural rule - never duplicated here),
52
+ * then persists exactly once. `supersedesBaselineClauseIds` is preserved
53
+ * exactly as authored; this function performs no baseline-reference
54
+ * resolution (that remains the evaluator's responsibility).
55
+ */
56
+ export declare function persistPerChangeContract(rawContract: unknown, options: PersistFrontendContractOptions): Promise<ApplicationPersistChangeContractResult>;
@@ -0,0 +1,91 @@
1
+ import path from 'node:path';
2
+ import { isValidPersistentBaselineContract, isValidPerChangeContract } from '../domain/frontendContracts.js';
3
+ import { DIAGNOSTIC_SEVERITY } from '../domain/diagnostics.js';
4
+ import { normalizeOutputLocation } from '../request/paths.js';
5
+ import { writePersistentBaselineContract, writePerChangeContract } from '../artifacts/frontendContractArtifactWriter.js';
6
+ import { readObservationArtifact } from '../artifacts/artifactReader.js';
7
+ import { MANIFEST_FILENAME as OBSERVATION_MANIFEST_FILENAME } from '../artifacts/artifactWriter.js';
8
+ function invalidRequest(message) {
9
+ return { ok: false, diagnostics: [{ code: 'invalid-request', severity: DIAGNOSTIC_SEVERITY['invalid-request'], message }] };
10
+ }
11
+ /**
12
+ * The one canonical application-level baseline-approval use case: `approve-baseline`
13
+ * is the *only* place a baseline is ever approved (see governing scope) - this
14
+ * function is never called from `observe`/`compare`/`evaluate-contract`, and a
15
+ * `PASS` evaluation never triggers it. Validates the raw (still-`unknown`,
16
+ * freshly-parsed-JSON) baseline through the existing frozen
17
+ * `isValidPersistentBaselineContract` structural gate (never a second
18
+ * validator), reads the supplied observation through the existing
19
+ * `readObservationArtifact` reader, verifies the baseline's frozen
20
+ * `sourceObservation` reference actually corresponds to that observation's
21
+ * stable identity, and - only then - persists exactly once through the
22
+ * existing contract writer. Never mutates the observation. Never infers,
23
+ * creates, or removes `supersedesBaselineId` - whatever the caller already
24
+ * authored is preserved exactly.
25
+ */
26
+ export async function approveAndPersistBaseline(rawContract, observationRoot, options) {
27
+ const validation = isValidPersistentBaselineContract(rawContract);
28
+ if (!validation.valid)
29
+ return invalidRequest(`baseline contract is invalid: ${validation.reason}`);
30
+ const contract = rawContract;
31
+ const observationRead = await readObservationArtifact(path.join(observationRoot, OBSERVATION_MANIFEST_FILENAME));
32
+ if (!observationRead.ok)
33
+ return invalidRequest(`observation artifact: ${observationRead.reason}`);
34
+ const observation = observationRead.artifact;
35
+ const ref = contract.sourceObservation;
36
+ const matchesObservation = ref.observationId === observation.observationId &&
37
+ ref.requestId === observation.requestId &&
38
+ ref.producer.name === observation.producer.name &&
39
+ ref.producer.version === observation.producer.version &&
40
+ ref.observationSchemaVersion === observation.schemaVersion;
41
+ if (!matchesObservation) {
42
+ return invalidRequest('baseline contract sourceObservation does not match the supplied observation artifact; refusing to approve an unrelated observation');
43
+ }
44
+ const normalized = normalizeOutputLocation(options.outputLocation);
45
+ if (!normalized.ok)
46
+ return { ok: false, diagnostics: [normalized.diagnostic] };
47
+ const persisted = await writePersistentBaselineContract(contract, normalized.value, options.cwd === undefined ? {} : { cwd: options.cwd });
48
+ if (!persisted.ok)
49
+ return { ok: false, diagnostics: persisted.diagnostics };
50
+ return {
51
+ ok: true,
52
+ baselineId: contract.baselineId,
53
+ ...(contract.supersedesBaselineId === undefined ? {} : { supersedesBaselineId: contract.supersedesBaselineId }),
54
+ artifactRoot: persisted.artifactRoot,
55
+ manifestPath: persisted.manifestPath,
56
+ clauseCount: contract.clauses.length,
57
+ };
58
+ }
59
+ /**
60
+ * The one canonical application-level per-change-contract persistence use
61
+ * case (`save-change-contract`) - persistence only, never approval
62
+ * terminology or semantics. Validates the raw contract through the existing
63
+ * frozen `isValidPerChangeContract` gate (which already rejects a
64
+ * `PersistentBaselineContract` passed here, an authored `category:
65
+ * "unexpected"`, and every other structural rule - never duplicated here),
66
+ * then persists exactly once. `supersedesBaselineClauseIds` is preserved
67
+ * exactly as authored; this function performs no baseline-reference
68
+ * resolution (that remains the evaluator's responsibility).
69
+ */
70
+ export async function persistPerChangeContract(rawContract, options) {
71
+ const validation = isValidPerChangeContract(rawContract);
72
+ if (!validation.valid)
73
+ return invalidRequest(`per-change contract is invalid: ${validation.reason}`);
74
+ const contract = rawContract;
75
+ const normalized = normalizeOutputLocation(options.outputLocation);
76
+ if (!normalized.ok)
77
+ return { ok: false, diagnostics: [normalized.diagnostic] };
78
+ const persisted = await writePerChangeContract(contract, normalized.value, options.cwd === undefined ? {} : { cwd: options.cwd });
79
+ if (!persisted.ok)
80
+ return { ok: false, diagnostics: persisted.diagnostics };
81
+ const supersedesBaselineClauseCount = contract.clauses.reduce((count, clause) => count + (clause.supersedesBaselineClauseIds?.length ?? 0), 0);
82
+ return {
83
+ ok: true,
84
+ contractId: contract.contractId,
85
+ artifactRoot: persisted.artifactRoot,
86
+ manifestPath: persisted.manifestPath,
87
+ clauseCount: contract.clauses.length,
88
+ supersedesBaselineClauseCount,
89
+ };
90
+ }
91
+ //# sourceMappingURL=frontendContractPersistenceService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontendContractPersistenceService.js","sourceRoot":"","sources":["../../src/application/frontendContractPersistenceService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { ComparisonArtifact } from '../domain/comparison.js';
2
+ export type ReadComparisonArtifactResult = {
3
+ ok: true;
4
+ artifact: ComparisonArtifact;
5
+ } | {
6
+ ok: false;
7
+ reason: string;
8
+ };
9
+ /**
10
+ * v0.5 Batch 3 addition: no canonical comparison-artifact reader existed
11
+ * before this batch (only `readObservationArtifact` did). Narrow counterpart
12
+ * to `artifacts/comparisonArtifactWriter.ts#writeComparisonArtifact`: reads
13
+ * one `manifest.json`, parses it, and validates it through the same
14
+ * `isValidComparisonArtifact` structural gate the writer uses - never a
15
+ * second validator, never a schema upgrade. Comparison schema remains
16
+ * `1.0.0`; this reader changes no comparison semantics.
17
+ */
18
+ export declare function readComparisonArtifact(manifestPath: string): Promise<ReadComparisonArtifactResult>;
@@ -0,0 +1,35 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { isValidComparisonArtifact } from '../domain/comparison.js';
3
+ /**
4
+ * v0.5 Batch 3 addition: no canonical comparison-artifact reader existed
5
+ * before this batch (only `readObservationArtifact` did). Narrow counterpart
6
+ * to `artifacts/comparisonArtifactWriter.ts#writeComparisonArtifact`: reads
7
+ * one `manifest.json`, parses it, and validates it through the same
8
+ * `isValidComparisonArtifact` structural gate the writer uses - never a
9
+ * second validator, never a schema upgrade. Comparison schema remains
10
+ * `1.0.0`; this reader changes no comparison semantics.
11
+ */
12
+ export async function readComparisonArtifact(manifestPath) {
13
+ let raw;
14
+ try {
15
+ raw = await readFile(manifestPath, 'utf8');
16
+ }
17
+ catch (err) {
18
+ const message = err instanceof Error ? err.message : String(err);
19
+ return { ok: false, reason: `failed to read comparison artifact manifest at "${manifestPath}": ${message}` };
20
+ }
21
+ let parsed;
22
+ try {
23
+ parsed = JSON.parse(raw);
24
+ }
25
+ catch (err) {
26
+ const message = err instanceof Error ? err.message : String(err);
27
+ return { ok: false, reason: `comparison artifact manifest at "${manifestPath}" is not valid JSON: ${message}` };
28
+ }
29
+ const validation = isValidComparisonArtifact(parsed);
30
+ if (!validation.valid) {
31
+ return { ok: false, reason: `comparison artifact manifest at "${manifestPath}" failed structural validation: ${validation.reason}` };
32
+ }
33
+ return { ok: true, artifact: parsed };
34
+ }
35
+ //# sourceMappingURL=comparisonArtifactReader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comparisonArtifactReader.js","sourceRoot":"","sources":["../../src/artifacts/comparisonArtifactReader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAIpE;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,YAAoB;IAC/D,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,mDAAmD,YAAY,MAAM,OAAO,EAAE,EAAE,CAAC;IAC/G,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oCAAoC,YAAY,wBAAwB,OAAO,EAAE,EAAE,CAAC;IAClH,CAAC;IAED,MAAM,UAAU,GAAG,yBAAyB,CAAC,MAAM,CAAC,CAAC;IACrD,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oCAAoC,YAAY,mCAAmC,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;IACvI,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAA4B,EAAE,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { PersistentBaselineContract, PerChangeContract } from '../domain/frontendContracts.js';
2
+ export type ReadPersistentBaselineContractResult = {
3
+ ok: true;
4
+ contract: PersistentBaselineContract;
5
+ } | {
6
+ ok: false;
7
+ reason: string;
8
+ };
9
+ export type ReadPerChangeContractResult = {
10
+ ok: true;
11
+ contract: PerChangeContract;
12
+ } | {
13
+ ok: false;
14
+ reason: string;
15
+ };
16
+ /**
17
+ * Narrow canonical reader counterpart to `frontendContractArtifactWriter.ts#writePersistentBaselineContract`:
18
+ * reads one `manifest.json`, parses it, and validates it through the same
19
+ * `isValidPersistentBaselineContract` structural gate the writer uses - never
20
+ * a second validator, never a schema upgrade, never a guessed missing field.
21
+ */
22
+ export declare function readPersistentBaselineContract(manifestPath: string): Promise<ReadPersistentBaselineContractResult>;
23
+ /** Narrow canonical reader counterpart to `frontendContractArtifactWriter.ts#writePerChangeContract`. */
24
+ export declare function readPerChangeContract(manifestPath: string): Promise<ReadPerChangeContractResult>;
@@ -0,0 +1,47 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { isValidPersistentBaselineContract, isValidPerChangeContract } from '../domain/frontendContracts.js';
3
+ async function readJson(manifestPath) {
4
+ let raw;
5
+ try {
6
+ raw = await readFile(manifestPath, 'utf8');
7
+ }
8
+ catch (err) {
9
+ const message = err instanceof Error ? err.message : String(err);
10
+ return { ok: false, reason: `failed to read artifact manifest at "${manifestPath}": ${message}` };
11
+ }
12
+ try {
13
+ return { ok: true, value: JSON.parse(raw) };
14
+ }
15
+ catch (err) {
16
+ const message = err instanceof Error ? err.message : String(err);
17
+ return { ok: false, reason: `artifact manifest at "${manifestPath}" is not valid JSON: ${message}` };
18
+ }
19
+ }
20
+ /**
21
+ * Narrow canonical reader counterpart to `frontendContractArtifactWriter.ts#writePersistentBaselineContract`:
22
+ * reads one `manifest.json`, parses it, and validates it through the same
23
+ * `isValidPersistentBaselineContract` structural gate the writer uses - never
24
+ * a second validator, never a schema upgrade, never a guessed missing field.
25
+ */
26
+ export async function readPersistentBaselineContract(manifestPath) {
27
+ const parsed = await readJson(manifestPath);
28
+ if (!parsed.ok)
29
+ return parsed;
30
+ const validation = isValidPersistentBaselineContract(parsed.value);
31
+ if (!validation.valid) {
32
+ return { ok: false, reason: `baseline contract manifest at "${manifestPath}" failed structural validation: ${validation.reason}` };
33
+ }
34
+ return { ok: true, contract: parsed.value };
35
+ }
36
+ /** Narrow canonical reader counterpart to `frontendContractArtifactWriter.ts#writePerChangeContract`. */
37
+ export async function readPerChangeContract(manifestPath) {
38
+ const parsed = await readJson(manifestPath);
39
+ if (!parsed.ok)
40
+ return parsed;
41
+ const validation = isValidPerChangeContract(parsed.value);
42
+ if (!validation.valid) {
43
+ return { ok: false, reason: `per-change contract manifest at "${manifestPath}" failed structural validation: ${validation.reason}` };
44
+ }
45
+ return { ok: true, contract: parsed.value };
46
+ }
47
+ //# sourceMappingURL=frontendContractArtifactReader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontendContractArtifactReader.js","sourceRoot":"","sources":["../../src/artifacts/frontendContractArtifactReader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,iCAAiC,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAK7G,KAAK,UAAU,QAAQ,CAAC,YAAoB;IAC1C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,wCAAwC,YAAY,MAAM,OAAO,EAAE,EAAE,CAAC;IACpG,CAAC;IACD,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;IAC9C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,yBAAyB,YAAY,wBAAwB,OAAO,EAAE,EAAE,CAAC;IACvG,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAAC,YAAoB;IACvE,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC5C,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,MAAM,CAAC;IAC9B,MAAM,UAAU,GAAG,iCAAiC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kCAAkC,YAAY,mCAAmC,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;IACrI,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAmC,EAAE,CAAC;AAC5E,CAAC;AAED,yGAAyG;AACzG,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,YAAoB;IAC9D,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC5C,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,MAAM,CAAC;IAC9B,MAAM,UAAU,GAAG,wBAAwB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1D,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oCAAoC,YAAY,mCAAmC,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;IACvI,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,KAA0B,EAAE,CAAC;AACnE,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { PersistentBaselineContract, PerChangeContract } from '../domain/frontendContracts.js';
2
+ import type { Diagnostic } from '../domain/diagnostics.js';
3
+ export declare const FRONTEND_CONTRACT_MANIFEST_FILENAME = "manifest.json";
4
+ /**
5
+ * Mirrors `PersistedComparisonResult`'s shape/semantics - a frontend contract
6
+ * directory has no screenshot file either, and this writer never touches
7
+ * `artifacts/artifactWriter.ts` or `artifacts/comparisonArtifactWriter.ts`.
8
+ */
9
+ export type PersistedFrontendContractResult = {
10
+ ok: true;
11
+ artifactRoot: string;
12
+ manifestPath: string;
13
+ } | {
14
+ ok: false;
15
+ diagnostics: Diagnostic[];
16
+ };
17
+ export interface WriteFrontendContractArtifactOptions {
18
+ /** Base directory that `outputLocation` is resolved against. Defaults to `process.cwd()`. */
19
+ cwd?: string;
20
+ }
21
+ /**
22
+ * Persists one already-valid `PersistentBaselineContract` as
23
+ * `<outputLocation>/<baselineId>/manifest.json`. Never mutates or removes any
24
+ * other baseline artifact - `supersedesBaselineId` (if present) is written
25
+ * exactly as supplied; this writer never inspects, rewrites, or deletes the
26
+ * baseline it refers to.
27
+ */
28
+ export declare function writePersistentBaselineContract(contract: PersistentBaselineContract, outputLocation: string, options?: WriteFrontendContractArtifactOptions): Promise<PersistedFrontendContractResult>;
29
+ /**
30
+ * Persists one already-valid `PerChangeContract` as
31
+ * `<outputLocation>/<contractId>/manifest.json`, symmetric with baseline
32
+ * persistence above (both share `CONTRACT_ARTIFACT_KIND`/`CONTRACT_SCHEMA_VERSION`).
33
+ */
34
+ export declare function writePerChangeContract(contract: PerChangeContract, outputLocation: string, options?: WriteFrontendContractArtifactOptions): Promise<PersistedFrontendContractResult>;