my-frontend-observer 0.4.0 → 0.6.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.
- package/CHANGELOG.md +96 -0
- package/README.md +52 -11
- package/dist/application/frontendContractEvaluationService.d.ts +49 -0
- package/dist/application/frontendContractEvaluationService.js +112 -0
- package/dist/application/frontendContractEvaluationService.js.map +1 -0
- package/dist/application/frontendContractPersistenceService.d.ts +56 -0
- package/dist/application/frontendContractPersistenceService.js +91 -0
- package/dist/application/frontendContractPersistenceService.js.map +1 -0
- package/dist/artifacts/comparisonArtifactReader.d.ts +18 -0
- package/dist/artifacts/comparisonArtifactReader.js +35 -0
- package/dist/artifacts/comparisonArtifactReader.js.map +1 -0
- package/dist/artifacts/frontendContractArtifactReader.d.ts +24 -0
- package/dist/artifacts/frontendContractArtifactReader.js +47 -0
- package/dist/artifacts/frontendContractArtifactReader.js.map +1 -0
- package/dist/artifacts/frontendContractArtifactWriter.d.ts +34 -0
- package/dist/artifacts/frontendContractArtifactWriter.js +70 -0
- package/dist/artifacts/frontendContractArtifactWriter.js.map +1 -0
- package/dist/artifacts/frontendContractEvaluationArtifactReader.d.ts +17 -0
- package/dist/artifacts/frontendContractEvaluationArtifactReader.js +34 -0
- package/dist/artifacts/frontendContractEvaluationArtifactReader.js.map +1 -0
- package/dist/artifacts/frontendContractEvaluationArtifactWriter.d.ts +32 -0
- package/dist/artifacts/frontendContractEvaluationArtifactWriter.js +58 -0
- package/dist/artifacts/frontendContractEvaluationArtifactWriter.js.map +1 -0
- package/dist/cli.js +496 -6
- package/dist/cli.js.map +1 -1
- package/dist/domain/boundedAgentContext.d.ts +174 -0
- package/dist/domain/boundedAgentContext.js +312 -0
- package/dist/domain/boundedAgentContext.js.map +1 -0
- package/dist/domain/boundedAgentContextCorrelation.d.ts +74 -0
- package/dist/domain/boundedAgentContextCorrelation.js +441 -0
- package/dist/domain/boundedAgentContextCorrelation.js.map +1 -0
- package/dist/domain/boundedAgentContextIdentity.d.ts +16 -0
- package/dist/domain/boundedAgentContextIdentity.js +53 -0
- package/dist/domain/boundedAgentContextIdentity.js.map +1 -0
- package/dist/domain/boundedAgentContextProjection.d.ts +60 -0
- package/dist/domain/boundedAgentContextProjection.js +449 -0
- package/dist/domain/boundedAgentContextProjection.js.map +1 -0
- package/dist/domain/frontendContractEvaluation.d.ts +57 -0
- package/dist/domain/frontendContractEvaluation.js +454 -0
- package/dist/domain/frontendContractEvaluation.js.map +1 -0
- package/dist/domain/frontendContractEvaluationArtifact.d.ts +65 -0
- package/dist/domain/frontendContractEvaluationArtifact.js +108 -0
- package/dist/domain/frontendContractEvaluationArtifact.js.map +1 -0
- package/dist/domain/frontendContractIdentity.d.ts +39 -0
- package/dist/domain/frontendContractIdentity.js +70 -0
- package/dist/domain/frontendContractIdentity.js.map +1 -0
- package/dist/domain/frontendContracts.d.ts +188 -0
- package/dist/domain/frontendContracts.js +260 -0
- package/dist/domain/frontendContracts.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +169 -10
- package/docs/CI_CD.md +68 -0
- package/docs/COMMANDS.md +162 -0
- package/docs/CONTRACTS.md +245 -5
- package/docs/CURRENT_STATE.md +152 -14
- package/docs/DEVELOPMENT.md +61 -11
- package/docs/DOCUMENTATION_PRESERVATION_POLICY.md +33 -33
- package/docs/PROJECT_DESCRIPTION.md +1770 -1770
- package/docs/PROJECT_MILESTONES.md +2073 -2073
- package/docs/PROJECT_OVERVIEW.md +29 -16
- package/docs/QUICKSTART.md +5 -0
- package/docs/RELEASE.md +17 -10
- package/docs/ROADMAP.md +9 -0
- package/docs/SECURITY.md +30 -1
- package/docs/WORKFLOWS.md +98 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,101 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0 - 2026-08-19
|
|
4
|
+
|
|
5
|
+
Bounded Agent Context and Native my-dev-kit Ecosystem Integration.
|
|
6
|
+
|
|
7
|
+
- Bounded runtime projection (`src/domain/boundedAgentContext.ts`,
|
|
8
|
+
`boundedAgentContextProjection.ts#projectBoundedAgentContext`): a
|
|
9
|
+
task-relevant, bounded view of page/viewport identity, stable targets,
|
|
10
|
+
geometry, runtime behavior, relationships, before/after differences,
|
|
11
|
+
contract results, requested/expected-dependent/protected/preserved scope
|
|
12
|
+
(reusing the existing v0.5 `frontendContracts.ts` types directly), and
|
|
13
|
+
screenshot/artifact references - never a raw evidence dump.
|
|
14
|
+
- Explicit adequacy reporting (`adequate`/`partial`/`inadequate` with
|
|
15
|
+
structured reason codes) and omission/truncation records, distinguishing
|
|
16
|
+
required from optional loss.
|
|
17
|
+
- Explicit runtime/static correlation
|
|
18
|
+
(`boundedAgentContextCorrelation.ts#deriveRuntimeStaticCorrelations`/
|
|
19
|
+
`attachRuntimeStaticCorrelations`): `correlated`/`ambiguous`/`unavailable`
|
|
20
|
+
outcomes only - a stable runtime target identity never silently becomes a
|
|
21
|
+
source-ownership claim, and competing candidates remain visible.
|
|
22
|
+
- Deterministic logical identity (`boundedAgentContextIdentity.ts`) distinct
|
|
23
|
+
from fresh per-execution instance identity.
|
|
24
|
+
- Public export/correlation boundary only: `src/index.ts` exports the full
|
|
25
|
+
bounded-agent-context and correlation type/function surface as a
|
|
26
|
+
programmatic library contract (bounded-agent-context schema `1.0.0`) - no
|
|
27
|
+
new CLI command, no disk artifact writer/reader, no `my-dev-kit` runtime
|
|
28
|
+
dependency, no orchestrator/lab code in this repository.
|
|
29
|
+
- Observation schema remains `1.2.0`, comparison schema `1.0.0`, frontend
|
|
30
|
+
contract schema `1.0.0`, evaluation artifact schema `1.0.0` - no existing
|
|
31
|
+
schema was bumped.
|
|
32
|
+
- Cross-platform packed-candidate validation: one hash-verified npm
|
|
33
|
+
candidate tarball (`acd067247c447294a611f37f52eab301b6038ab1c6d493ae65e81c2f1279bfd7`)
|
|
34
|
+
proven on Windows, Linux, and macOS, including an installed-package smoke
|
|
35
|
+
of the new bounded-agent-context projection and runtime/static
|
|
36
|
+
correlation exports alongside every pre-existing v0.1-v0.5 packed
|
|
37
|
+
behavior.
|
|
38
|
+
|
|
39
|
+
## 0.5.0 - 2026-08-13
|
|
40
|
+
|
|
41
|
+
Executable Frontend Contracts and Explicit Change Scope.
|
|
42
|
+
|
|
43
|
+
- Two related contract classes: a `PersistentBaselineContract` (previously
|
|
44
|
+
approved frontend behavior that stays active across future changes unless
|
|
45
|
+
explicitly superseded, with append-based supersession history) and a
|
|
46
|
+
`PerChangeContract` (the allowed scope of one requested change).
|
|
47
|
+
- Four authored change-scope categories - `requested`, `expected-dependent`
|
|
48
|
+
(`required` or `permitted`), `protected`, `preserved` - plus a fifth,
|
|
49
|
+
strictly derived-only classification, `unexpected`, for a meaningful
|
|
50
|
+
rendered difference no active clause accounts for. `unexpected` can never
|
|
51
|
+
be authored as a permission.
|
|
52
|
+
- A closed, bounded vocabulary of 15 contract primitives (visibility,
|
|
53
|
+
clipping, width bounds, non-overlap, relative width, vertical sequence,
|
|
54
|
+
geometric fit, document-width-vs-viewport, scroll ownership, initial-
|
|
55
|
+
viewport position, relationship-unchanged, and property-unchanged/
|
|
56
|
+
increases/decreases) and three contract tolerances (`exact`,
|
|
57
|
+
`absolute-px`, `percent`) - independent of `compare`'s geometry tolerance,
|
|
58
|
+
which only suppresses insignificant noise and is never contract
|
|
59
|
+
authorization.
|
|
60
|
+
- Explicit, never-inferred baseline and per-change clause supersession; two
|
|
61
|
+
clauses that structurally contradict each other without explicit
|
|
62
|
+
supersession produce a `conflict` result rather than a silent preference.
|
|
63
|
+
- One canonical evaluation engine (`evaluateFrontendContract`) that owns
|
|
64
|
+
requested/expected-dependent/protected/preserved evaluation, unexpected-
|
|
65
|
+
change derivation, and the overall `PASS`/`FAIL` verdict - reusing existing
|
|
66
|
+
v0.4 observation/comparison evidence directly, never re-launching a
|
|
67
|
+
browser, re-resolving a target, or reimplementing relationship/clipping
|
|
68
|
+
derivation.
|
|
69
|
+
- Actionable per-clause results (`pass`/`fail`/`unavailable` with a required
|
|
70
|
+
reason/`conflict` with at least two conflicting clause identities) - never
|
|
71
|
+
an opaque score.
|
|
72
|
+
- Atomic, independently-versioned persistence for baseline contracts,
|
|
73
|
+
per-change contracts, and evaluation results, with no destructive artifact
|
|
74
|
+
overwrite, no copied screenshots, and full source observation/comparison/
|
|
75
|
+
contract immutability.
|
|
76
|
+
- Three new public commands: `approve-baseline` (the only baseline-approval
|
|
77
|
+
act - explicit only, never inferred from `compare` or a `PASS`
|
|
78
|
+
evaluation), `save-change-contract` (persistence only), and
|
|
79
|
+
`evaluate-contract` (runs the canonical evaluator against already-
|
|
80
|
+
persisted evidence and persists exactly one evaluation artifact).
|
|
81
|
+
`evaluate-contract --enforce` makes an already-persisted `FAIL` verdict
|
|
82
|
+
produce a nonzero process exit status without changing the verdict, its
|
|
83
|
+
identity, or its persisted content - a `FAIL` without `--enforce` still
|
|
84
|
+
exits `0`.
|
|
85
|
+
- Proven against real Chromium observations, not hand-constructed
|
|
86
|
+
artifacts: a fully successful contract change, and the "milestone
|
|
87
|
+
signature" case - a locally successful requested change coexisting with a
|
|
88
|
+
genuine protected-property regression and a genuine preserved-invariant
|
|
89
|
+
regression - producing overall `FAIL`.
|
|
90
|
+
- Frontend contract schema `1.0.0` and evaluation artifact schema `1.0.0`,
|
|
91
|
+
each its own independent schema family; observation schema remains
|
|
92
|
+
`1.2.0` and comparison schema remains `1.0.0`.
|
|
93
|
+
- Cross-platform packed-candidate validation: one hash-verified npm
|
|
94
|
+
candidate tarball proven on Windows, Linux, and macOS, covering the
|
|
95
|
+
installed candidate's `approve-baseline`, `save-change-contract`, and
|
|
96
|
+
`evaluate-contract` commands alongside every pre-existing v0.1-v0.4
|
|
97
|
+
packed observation/comparison behavior.
|
|
98
|
+
|
|
3
99
|
## 0.4.0 - 2026-08-12
|
|
4
100
|
|
|
5
101
|
Layout Relationships, Dependency Evidence, and Before/After Comparison.
|
package/README.md
CHANGED
|
@@ -6,15 +6,22 @@ in [docs/PROJECT_DESCRIPTION.md](docs/PROJECT_DESCRIPTION.md).
|
|
|
6
6
|
|
|
7
7
|
## Current status
|
|
8
8
|
|
|
9
|
-
`v0.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
(comparison schema
|
|
9
|
+
`v0.6.0`, Bounded Agent Context and Native my-dev-kit Ecosystem Integration,
|
|
10
|
+
is the current published release. It builds on `v0.5.0`, Executable Frontend
|
|
11
|
+
Contracts and Explicit Change Scope: `my-frontend-observer observe` launches a real,
|
|
12
|
+
sandboxed Chromium browser, enforces a loopback-only safety policy, captures
|
|
13
|
+
a viewport screenshot plus bounded page/target evidence, and persists it as
|
|
14
|
+
one portable `manifest.json` + `screenshot.png` artifact (observation schema
|
|
15
|
+
`1.2.0`). `my-frontend-observer compare` reads two already-persisted
|
|
16
|
+
observation artifacts and derives before/after evidence purely from their
|
|
17
|
+
existing content, persisting a comparison artifact (comparison schema
|
|
18
|
+
`1.0.0`). `my-frontend-observer approve-baseline`, `save-change-contract`,
|
|
19
|
+
and `evaluate-contract` turn that evidence into an executable frontend
|
|
20
|
+
contract: an explicitly approved baseline plus a per-change contract
|
|
21
|
+
(requested/expected-dependent/protected/preserved scope) are evaluated
|
|
22
|
+
together into one `PASS`/`FAIL` verdict, so a locally successful requested
|
|
23
|
+
change can never silently hide a protected-region regression (frontend
|
|
24
|
+
contract schema `1.0.0`; evaluation artifact schema `1.0.0`).
|
|
18
25
|
|
|
19
26
|
Install:
|
|
20
27
|
|
|
@@ -91,6 +98,40 @@ including when the two observations turn out to be `incomparable`, which is
|
|
|
91
98
|
itself a successful comparison outcome. See
|
|
92
99
|
[docs/COMMANDS.md](docs/COMMANDS.md) for the full flag reference.
|
|
93
100
|
|
|
101
|
+
### Frontend contracts
|
|
102
|
+
|
|
103
|
+
`v0.5.0` ships a text/config-driven frontend contract and evaluation
|
|
104
|
+
workflow: approve a baseline against an observation, save a per-change
|
|
105
|
+
contract, then evaluate a candidate change against them plus existing
|
|
106
|
+
before/after/comparison evidence, deriving one `PASS`/`FAIL` verdict:
|
|
107
|
+
|
|
108
|
+
```powershell
|
|
109
|
+
my-frontend-observer approve-baseline --observation observations/<id> --contract-file baseline.json --output baselines
|
|
110
|
+
my-frontend-observer save-change-contract --contract-file change.json --output contracts
|
|
111
|
+
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]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
(From a source checkout, use `node dist/cli.js approve-baseline ...` etc.
|
|
115
|
+
instead.)
|
|
116
|
+
|
|
117
|
+
`evaluate-contract` never launches a browser or recomputes comparison
|
|
118
|
+
evidence. `--enforce` only changes the process exit status for a `FAIL`
|
|
119
|
+
verdict; the verdict itself, and its persisted evidence, are unaffected. See
|
|
120
|
+
[docs/COMMANDS.md](docs/COMMANDS.md) for the full flag reference and
|
|
121
|
+
[docs/WORKFLOWS.md](docs/WORKFLOWS.md) for the end-to-end flow.
|
|
122
|
+
|
|
123
|
+
### Bounded agent context (v0.6.0)
|
|
124
|
+
|
|
125
|
+
`src/domain/boundedAgentContext.ts`, `boundedAgentContextProjection.ts`,
|
|
126
|
+
`boundedAgentContextCorrelation.ts`, and `boundedAgentContextIdentity.ts`
|
|
127
|
+
ship a programmatic (library-only, no CLI command) bounded runtime
|
|
128
|
+
projection and an explicit runtime/static correlation boundary
|
|
129
|
+
(`correlated`/`ambiguous`/`unavailable`, never inferred source ownership),
|
|
130
|
+
exported from `src/index.ts` (bounded-agent-context schema `1.0.0`). See
|
|
131
|
+
[docs/CONTRACTS.md](docs/CONTRACTS.md) for the exact contract and
|
|
132
|
+
[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for how it fits the existing
|
|
133
|
+
pipeline.
|
|
134
|
+
|
|
94
135
|
Validation:
|
|
95
136
|
|
|
96
137
|
```powershell
|
|
@@ -108,8 +149,8 @@ Planning authorities:
|
|
|
108
149
|
intent and responsibility boundaries.
|
|
109
150
|
- [Project Milestones](docs/PROJECT_MILESTONES.md): complete ordered capability
|
|
110
151
|
design and cross-milestone rules.
|
|
111
|
-
- [ROADMAP](docs/ROADMAP.md): version-level requirements; v0.1-v0.
|
|
112
|
-
released, v0.
|
|
152
|
+
- [ROADMAP](docs/ROADMAP.md): version-level requirements; v0.1-v0.6 are
|
|
153
|
+
released, v0.7+ remain future.
|
|
113
154
|
- [Current State](docs/CURRENT_STATE.md): retained scaffold and release state.
|
|
114
155
|
|
|
115
156
|
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>;
|