halfcycle 0.3.32 → 0.3.33
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/.claude-plugin/plugin.json +1 -1
- package/bin/bin.bundle.mjs +45 -25
- package/dist/bin.d.ts +1 -1
- package/dist/bin.js +1147 -788
- package/dist/bin.js.map +3 -3
- package/dist/build-record/index.d.ts +14 -36
- package/dist/build-record/index.d.ts.map +1 -1
- package/dist/build-record/obtain.d.ts +81 -0
- package/dist/build-record/obtain.d.ts.map +1 -0
- package/dist/build-record/render-v2.d.ts +47 -0
- package/dist/build-record/render-v2.d.ts.map +1 -0
- package/dist/build-record/write.d.ts +77 -60
- package/dist/build-record/write.d.ts.map +1 -1
- package/dist/close-phase.d.ts +59 -16
- package/dist/close-phase.d.ts.map +1 -1
- package/dist/index.js +799 -571
- package/dist/index.js.map +3 -3
- package/package.json +3 -1
- package/dist/build-record/assemble.d.ts +0 -37
- package/dist/build-record/assemble.d.ts.map +0 -1
- package/dist/build-record/close-record.d.ts +0 -124
- package/dist/build-record/close-record.d.ts.map +0 -1
- package/dist/build-record/sources.d.ts +0 -111
- package/dist/build-record/sources.d.ts.map +0 -1
- package/dist/build-record/template.d.ts +0 -29
- package/dist/build-record/template.d.ts.map +0 -1
- package/dist/build-record/types.d.ts +0 -200
- package/dist/build-record/types.d.ts.map +0 -1
|
@@ -1,42 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @halfcycle/bundle — Build
|
|
2
|
+
* @halfcycle/bundle — the Build Record's client half.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* the
|
|
4
|
+
* Closing a phase freezes its record on the service; the CLI fetches that frozen
|
|
5
|
+
* record and writes it into the client's repository as
|
|
6
|
+
* `docs/build-records/<segment>.{json,md}`. Nothing here assembles a record from
|
|
7
|
+
* local files: the local assembler, its `.workbench` close record and every local
|
|
8
|
+
* source it read left the shipped CLI in Connect Phase 4
|
|
9
|
+
* (`close-produces-the-record`), which is what removed the orchestration-state
|
|
10
|
+
* requirement no Connect client could meet (`#1400`).
|
|
8
11
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* never appear (the wire types physically lack those fields).
|
|
13
|
-
* INV-001 — every source is a LOCAL file; no hosted store / control-plane
|
|
14
|
-
* import or call. `packages/bundle` ⊆ {core, events}.
|
|
12
|
+
* INV-001 — this module and everything it re-exports import only
|
|
13
|
+
* `@halfcycle/events` and Node builtins. The record's schema, the response schema
|
|
14
|
+
* and the phase grammar each have one home there; nothing here restates them.
|
|
15
15
|
*/
|
|
16
|
-
export { BUILD_RECORD_FORMAT, projectSeedGuard, type SeedGuard, type Delivered, type DoneWhenResult, type Acceptance, type GatesEarnedKeep, type Instruments, type Tasks, type BuildRecordV1, type OrchestrationStateSource, type NarratedSource, type PhaseCloseSource, type BuildRecordSources, } from './types.js';
|
|
17
16
|
export { isValidPhaseIdentity, assertValidPhaseIdentity, InvalidPhaseIdentityError, renderPhaseSegment, type PhaseIdentity, } from '@halfcycle/events';
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
20
|
-
export {
|
|
21
|
-
export { renderBuildRecordMarkdown } from './template.js';
|
|
22
|
-
export { BUILD_RECORD_DIR, serialiseRecordJson, writeBuildRecord, type WriteResult, } from './write.js';
|
|
23
|
-
import { type WriteResult } from './write.js';
|
|
24
|
-
import type { NarratedSource } from './types.js';
|
|
25
|
-
import type { PhaseIdentity } from '@halfcycle/events';
|
|
26
|
-
/**
|
|
27
|
-
* Assemble + write the Build Record for a phase from its local artefacts.
|
|
28
|
-
*
|
|
29
|
-
* Idempotent (INV-009): a deterministic function of the current artefacts —
|
|
30
|
-
* re-running overwrites the pair with the same bytes if the inputs are
|
|
31
|
-
* unchanged. Fails loud (throws `MissingSourceError`) if any required source is
|
|
32
|
-
* absent — never blanks or invents a field.
|
|
33
|
-
*/
|
|
34
|
-
export declare function assemblePhaseBuildRecord(opts: {
|
|
35
|
-
repoRoot: string;
|
|
36
|
-
phasesDir: string;
|
|
37
|
-
guardEvalLogDir: string;
|
|
38
|
-
phaseId: PhaseIdentity;
|
|
39
|
-
narrated: NarratedSource;
|
|
40
|
-
touchedInvariants: string[];
|
|
41
|
-
}): WriteResult;
|
|
17
|
+
export { renderBuildRecordV2Markdown } from './render-v2.js';
|
|
18
|
+
export { BUILD_RECORD_DIR, NODE_RECORD_PAIR_IO, serialiseServedRecord, writeRecordPair, type RecordPairIo, type RecordPairResult, } from './write.js';
|
|
19
|
+
export { REFUSAL_FOLLOW_UP, REFUSAL_REASONS, buildRecordUrl, obtainBuildRecord, refetchCommand, type ObtainOutcome, } from './obtain.js';
|
|
42
20
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/build-record/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/build-record/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,KAAK,aAAa,GACnB,MAAM,mBAAmB,CAAC;AAK3B,OAAO,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,KAAK,YAAY,EACjB,KAAK,gBAAgB,GACtB,MAAM,YAAY,CAAC;AAIpB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch a closed phase's Build Record from the Halfcycle service and write the pair
|
|
3
|
+
* into the client's repository — the one path both `close-phase` (after a close)
|
|
4
|
+
* and `build-record <phase>` (the re-fetch) take, so the two cannot drift.
|
|
5
|
+
*
|
|
6
|
+
* ONE RESPONSE-HANDLING RULE. A body that parses under `phaseRecordResponseSchema`
|
|
7
|
+
* is authoritative, whatever its status. Otherwise a 404 whose body is the
|
|
8
|
+
* service's own missing-route answer (`error: 'Not Found'`, `Route GET … not
|
|
9
|
+
* found.`) means the service predates Build Records. Anything else — including the
|
|
10
|
+
* route's own 404 for an engagement that is gone — is reported by its status and
|
|
11
|
+
* the service's own sentence, never reworded as a missing route.
|
|
12
|
+
*
|
|
13
|
+
* THE PATH IS THE PHASE ARGUMENT'S (INV-023). The served `phase` is compared with
|
|
14
|
+
* the one asked for and a mismatch writes nothing; the writer then builds its path
|
|
15
|
+
* from the argument alone.
|
|
16
|
+
*
|
|
17
|
+
* WHAT IS WRITTEN IS WHAT ARRIVED (INV-007). The `.json` is the served record as
|
|
18
|
+
* `JSON.parse` returned it, key order included — not the schema's output, which
|
|
19
|
+
* would reorder keys. The schema parse decides only whether it is written at all.
|
|
20
|
+
*
|
|
21
|
+
* INV-001: Node builtins, the global `fetch` and `@halfcycle/events` only.
|
|
22
|
+
* SHIPS TO CLIENT DISK inside the CLI: every string here is a client-facing
|
|
23
|
+
* sentence, and no comment sits beside an object member.
|
|
24
|
+
*/
|
|
25
|
+
import { type PhaseRecordRefusalReason } from '@halfcycle/events';
|
|
26
|
+
import type { RepoCredential } from '../open-phase.js';
|
|
27
|
+
import { type RecordPairIo } from './write.js';
|
|
28
|
+
/**
|
|
29
|
+
* What came of asking for a phase's record. `written`: the pair is on disk (paths
|
|
30
|
+
* relative to the repository). `exists`: a file this CLI will not replace is in the
|
|
31
|
+
* way; nothing was written. `withheld` and `refused`: the service's answer, with its
|
|
32
|
+
* own sentence. `unavailable`: the record could not be obtained, and `why` says so.
|
|
33
|
+
*/
|
|
34
|
+
export type ObtainOutcome = {
|
|
35
|
+
kind: 'written';
|
|
36
|
+
jsonPath: string;
|
|
37
|
+
mdPath: string;
|
|
38
|
+
} | {
|
|
39
|
+
kind: 'exists';
|
|
40
|
+
path: string;
|
|
41
|
+
} | {
|
|
42
|
+
kind: 'withheld';
|
|
43
|
+
message: string;
|
|
44
|
+
} | {
|
|
45
|
+
kind: 'refused';
|
|
46
|
+
reason: PhaseRecordRefusalReason;
|
|
47
|
+
message: string;
|
|
48
|
+
} | {
|
|
49
|
+
kind: 'unavailable';
|
|
50
|
+
why: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* The line the CLI adds after the service's own refusal sentence, per reason. The
|
|
54
|
+
* key set is the reason enum's: the type refuses a missing key at build time, and
|
|
55
|
+
* `close-produces-the-record.test.ts` compares the keys with
|
|
56
|
+
* `phaseRecordRefusalReasonSchema.options` read from the schema.
|
|
57
|
+
*/
|
|
58
|
+
export declare const REFUSAL_FOLLOW_UP: {
|
|
59
|
+
readonly 'still-open': "Nothing was written. The record is produced when the phase closes.";
|
|
60
|
+
readonly reopened: "Nothing was written. A new record is produced when the phase closes again.";
|
|
61
|
+
readonly 'never-opened': "Nothing was written. Check the name: it must match the phase name exactly as it was opened.";
|
|
62
|
+
readonly 'never-closed': "Nothing was written.";
|
|
63
|
+
readonly 'closed-before-records': "Nothing was written.";
|
|
64
|
+
};
|
|
65
|
+
/** The reason enum's options, read from the schema, for the key-set check. */
|
|
66
|
+
export declare const REFUSAL_REASONS: readonly PhaseRecordRefusalReason[];
|
|
67
|
+
/** The address of a phase's record: the phase is one path segment, encoded once. */
|
|
68
|
+
export declare function buildRecordUrl(credential: RepoCredential, phase: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* Ask the service for `phase`'s frozen record and, when it serves one, write the
|
|
71
|
+
* pair under `repoRoot`. Never throws: every way this can fail — the network, the
|
|
72
|
+
* service, a write — is an outcome the caller prints, because by the time this
|
|
73
|
+
* runs after a close the phase has already closed.
|
|
74
|
+
*/
|
|
75
|
+
export declare function obtainBuildRecord(credential: RepoCredential, repoRoot: string, phase: string, io?: RecordPairIo): Promise<ObtainOutcome>;
|
|
76
|
+
/**
|
|
77
|
+
* How to fetch a phase's record later, as a line a person can paste: the phase is
|
|
78
|
+
* quoted for the shell unless it is plain letters, digits and `._+-`.
|
|
79
|
+
*/
|
|
80
|
+
export declare function refetchCommand(phase: string): string;
|
|
81
|
+
//# sourceMappingURL=obtain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"obtain.d.ts","sourceRoot":"","sources":["../../src/build-record/obtain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAwC,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAErF;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,wBAAwB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB;;;;;;CAM+B,CAAC;AAE9D,8EAA8E;AAC9E,eAAO,MAAM,eAAe,EAAE,SAAS,wBAAwB,EAA2C,CAAC;AAE3G,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,UAAU,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAKhF;AASD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,cAAc,EAC1B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,EAAE,GAAE,YAAkC,GACrC,OAAO,CAAC,aAAa,CAAC,CA0ExB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGpD"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The v2 Build Record markdown renderer: a pure function from a parsed
|
|
3
|
+
* `halfcycle-build-record/v2` record to the markdown half of the pair.
|
|
4
|
+
*
|
|
5
|
+
* It owns every word a reader of the `.md` sees, and the rule that keeps the
|
|
6
|
+
* client's words and the phase name from forging the observed register. The two
|
|
7
|
+
* registers are told apart by position: everything Halfcycle's own systems
|
|
8
|
+
* recorded prints as plain lines under an "Observed by Halfcycle" label, and
|
|
9
|
+
* everything the client stated prints as quoted lines, and only quoted lines,
|
|
10
|
+
* under a "Stated by the client" label. No other quoted line exists.
|
|
11
|
+
*
|
|
12
|
+
* PURE. It reads no clock, environment or locale. Numbers are formatted with
|
|
13
|
+
* `String()`, never a locale-aware formatter; `closedAt` is reworded by text
|
|
14
|
+
* substitution, never through `Date`. It iterates only keys it names, keeps array
|
|
15
|
+
* order, and sorts closes by `sequence` only. The same record gives the same
|
|
16
|
+
* bytes, whatever key order a database hands back.
|
|
17
|
+
*
|
|
18
|
+
* IT DOES NOT PARSE. The caller parses the record first; the type here is the
|
|
19
|
+
* contract's own inferred type, imported as a type so nothing of the contract
|
|
20
|
+
* module is pulled into this file at runtime. The one cross-half rule it relies
|
|
21
|
+
* on — both halves describe the same closes, paired by `sequence` — it checks
|
|
22
|
+
* itself and throws on, because printing a close without its other half would
|
|
23
|
+
* put a blank where a statement belongs.
|
|
24
|
+
*
|
|
25
|
+
* EVERY STRING HERE SHIPS. The fixed prose is printed into the client's own
|
|
26
|
+
* repository, so it is written in client words and carries no internal
|
|
27
|
+
* vocabulary; the tests scan the emitted markdown, not this source. No comment
|
|
28
|
+
* here sits beside an object member, because the bundler keeps those verbatim.
|
|
29
|
+
*/
|
|
30
|
+
import type { BuildRecordV2 } from '@halfcycle/events';
|
|
31
|
+
/**
|
|
32
|
+
* Print one string from the record as inert, visible markdown text.
|
|
33
|
+
*
|
|
34
|
+
* Per code point: a hidden character becomes `U+` and its code in uppercase hex,
|
|
35
|
+
* at least four digits; a markdown special character is escaped with a
|
|
36
|
+
* backslash; everything else prints as itself. An empty string prints
|
|
37
|
+
* `(blank)`, so a slot is never silently empty.
|
|
38
|
+
*/
|
|
39
|
+
export declare function printRecordText(text: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Render a parsed v2 Build Record as markdown.
|
|
42
|
+
*
|
|
43
|
+
* Throws, in client words, if the two halves describe different closes — which a
|
|
44
|
+
* parsed record cannot do. Output uses `\n` only and ends in exactly one `\n`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function renderBuildRecordV2Markdown(record: BuildRecordV2): string;
|
|
47
|
+
//# sourceMappingURL=render-v2.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-v2.d.ts","sourceRoot":"","sources":["../../src/build-record/render-v2.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAyBvD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAapD;AAoQD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAyDzE"}
|
|
@@ -1,70 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The Build Record pair writer — what a close and the re-fetch write.
|
|
3
3
|
*
|
|
4
|
-
* Blast radius: the writer touches ONLY `docs/build-records/` in the target
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Blast radius: the writer touches ONLY `docs/build-records/` in the target repo,
|
|
5
|
+
* the one path `WRITE_ALLOWLIST` gives the Build Record (INV-019), and it enforces
|
|
6
|
+
* that rather than claiming it (INV-023).
|
|
7
7
|
*
|
|
8
|
-
* THE
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* that caller the assertion below is the ONLY guard.
|
|
8
|
+
* THE PATH COMES FROM THE CALLER'S PHASE ARGUMENT, never from the record. The
|
|
9
|
+
* identity is decided by `isValidPhaseIdentity`, which has one home in
|
|
10
|
+
* `@halfcycle/events`; the segment is `renderPhaseSegment`'s, from the same home.
|
|
11
|
+
* This module restates neither rule.
|
|
13
12
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* so is better than leaving a reader to discover it. The residual refusal for a
|
|
25
|
-
* library caller is now the `isValidPhaseIdentity` check in `writeBuildRecord`
|
|
26
|
-
* below, which throws `InvalidPhaseIdentityError` naming this module's allowed
|
|
27
|
-
* location — same error and same message a caller entering through
|
|
28
|
-
* `validateNarrated` gets. `assertInsideOutDir` still runs on every write, but it
|
|
29
|
-
* can no longer be the thing that fires on a hostile identity, because a hostile
|
|
30
|
-
* identity never reaches the join: the identity check refuses it and the renderer
|
|
31
|
-
* refuses it again.
|
|
32
|
-
*
|
|
33
|
-
* SO THE RESIDUAL GUARD IS UNREACHABLE FROM THIS PACKAGE, and that is measured
|
|
34
|
-
* rather than assumed: deleting both of its call sites leaves this package's
|
|
35
|
-
* whole suite green, and sixteen adversarial identities that pass the predicate
|
|
36
|
-
* were probed without one reaching it. What it still guards is real and it is
|
|
37
|
-
* CROSS-PACKAGE — the RENDERER'S OUTPUT. Every segment the renderer emits today
|
|
38
|
-
* is a plain basename, so an edit in `packages/events` that let a separator into
|
|
39
|
-
* one is the only way this can fire, and that edit is invisible from here. A
|
|
40
|
-
* guard whose subject lives in another package cannot be exercised by a
|
|
41
|
-
* behavioural test of this one, so it is exported and tested directly rather
|
|
42
|
-
* than left module-private, where nothing could reach it and nothing would
|
|
43
|
-
* notice its deletion.
|
|
44
|
-
*
|
|
45
|
-
* Refusal is ALL-OR-NOTHING because both paths are asserted BEFORE the first
|
|
46
|
-
* `writeFileSync` — and before the `mkdirSync`, so a refused record does not
|
|
47
|
-
* leave an empty directory behind either. There is no rollback and no two-phase
|
|
48
|
-
* commit; nothing here asks for one.
|
|
13
|
+
* THE RESIDUAL CHECK, AND WHY IT IS EXPORTED. `assertInsideOutDir` runs on every
|
|
14
|
+
* path, temporary ones included, and compares two operands that move
|
|
15
|
+
* independently: the allowed directory (from `repoRoot` and a constant) and the
|
|
16
|
+
* candidate path (from the phase). A hostile phase never reaches it — the
|
|
17
|
+
* identity check refuses first and the renderer refuses again — so what it still
|
|
18
|
+
* guards is CROSS-PACKAGE: a rendered segment that grew a separator, which only an
|
|
19
|
+
* edit in `packages/events` could produce and which no behavioural test of this
|
|
20
|
+
* package can arrange. It is therefore exported and called directly by
|
|
21
|
+
* `build-record-write-discipline.test.ts`, the only control that can tell it
|
|
22
|
+
* from a deleted one.
|
|
49
23
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
24
|
+
* Refusal is ALL-OR-NOTHING: every path is asserted, and both texts are rendered,
|
|
25
|
+
* before the `mkdirSync` and the first create, so a refused record leaves no file
|
|
26
|
+
* and no empty directory behind.
|
|
53
27
|
*/
|
|
54
|
-
import type
|
|
28
|
+
import { type BuildRecordV2 } from '@halfcycle/events';
|
|
55
29
|
/** The one and only output directory (relative to the target repo root). */
|
|
56
30
|
export declare const BUILD_RECORD_DIR: string;
|
|
57
|
-
export interface WriteResult {
|
|
58
|
-
jsonPath: string;
|
|
59
|
-
mdPath: string;
|
|
60
|
-
}
|
|
61
|
-
/** Serialise the record JSON deterministically (2-space indent + trailing NL). */
|
|
62
|
-
export declare function serialiseRecordJson(record: BuildRecordV1): string;
|
|
63
31
|
/**
|
|
64
32
|
* Assert that a joined output path is still a plain file directly inside
|
|
65
33
|
* `outDir`. `relative` is computed between two values that move independently —
|
|
66
|
-
* the allowed directory, and a candidate path built from the
|
|
67
|
-
* comparison can actually fail.
|
|
34
|
+
* the allowed directory, and a candidate path built from the caller's phase — so
|
|
35
|
+
* this comparison can actually fail.
|
|
68
36
|
*
|
|
69
37
|
* EXPORTED SO IT CAN BE TESTED AT ALL. Its subject is a filename segment that
|
|
70
38
|
* grew a separator, which only an edit in another package can produce, so no
|
|
@@ -75,9 +43,58 @@ export declare function serialiseRecordJson(record: BuildRecordV1): string;
|
|
|
75
43
|
*/
|
|
76
44
|
export declare function assertInsideOutDir(outDir: string, candidate: string, identity: unknown): void;
|
|
77
45
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* file
|
|
46
|
+
* The file operations `writeRecordPair` makes, behind one seam so a test can make
|
|
47
|
+
* the SECOND write fail and watch the first come back out. `createExclusive` opens
|
|
48
|
+
* with `wx` (never truncates an existing file) and removes what it created if the
|
|
49
|
+
* write itself fails; `rename` replaces in one step on the same filesystem.
|
|
50
|
+
*/
|
|
51
|
+
export interface RecordPairIo {
|
|
52
|
+
createExclusive(path: string, text: string): void;
|
|
53
|
+
rename(from: string, to: string): void;
|
|
54
|
+
}
|
|
55
|
+
export declare const NODE_RECORD_PAIR_IO: RecordPairIo;
|
|
56
|
+
/**
|
|
57
|
+
* What `writeRecordPair` did. `created`: neither file existed. `up-to-date`: the
|
|
58
|
+
* `.json` already held these exact bytes, and the `.md` was rendered again.
|
|
59
|
+
* `replaced`: an earlier record of this same phase was replaced. `in-the-way`:
|
|
60
|
+
* a file this CLI did not write, or cannot recognise as this phase's earlier
|
|
61
|
+
* record, is at one of the two paths — nothing was written and `path` names it.
|
|
62
|
+
*/
|
|
63
|
+
export type RecordPairResult = {
|
|
64
|
+
kind: 'created' | 'up-to-date' | 'replaced';
|
|
65
|
+
jsonPath: string;
|
|
66
|
+
mdPath: string;
|
|
67
|
+
} | {
|
|
68
|
+
kind: 'in-the-way';
|
|
69
|
+
path: string;
|
|
70
|
+
};
|
|
71
|
+
/** Serialise a served record for the `.json`: 2-space indent, trailing newline. */
|
|
72
|
+
export declare function serialiseServedRecord(rawRecord: unknown): string;
|
|
73
|
+
/**
|
|
74
|
+
* Write a phase's Build Record pair into `docs/build-records/`: the `.json` as the
|
|
75
|
+
* service sent it, and the `.md` rendered from the parsed record.
|
|
76
|
+
*
|
|
77
|
+
* THE PATH COMES FROM `phase`, THE CALLER'S VALIDATED ARGUMENT, and never from
|
|
78
|
+
* `record.phase` (INV-023). The caller compares the two before it gets here; this
|
|
79
|
+
* function does not rely on that, so a record naming `../../x` still lands at the
|
|
80
|
+
* argument's path.
|
|
81
|
+
*
|
|
82
|
+
* EXCLUSIVE CREATE IS THE EXISTENCE CHECK. There is no separate look first, which
|
|
83
|
+
* would leave a window between looking and writing. `.json` is opened `wx`, then
|
|
84
|
+
* `.md`; on `EEXIST` from either, any `.json` this call created is removed and the
|
|
85
|
+
* replace check decides: (a) the same `.json` bytes — up to date, and the `.md` is
|
|
86
|
+
* rendered again so a renderer change never refuses the CLI's own pair; (b) this
|
|
87
|
+
* phase's own earlier record — both files are replaced; (c) anything else —
|
|
88
|
+
* nothing is written and the file in the way is named.
|
|
89
|
+
*
|
|
90
|
+
* NO HALF PAIR. A replace writes `<final name>.tmp` beside each file and renames
|
|
91
|
+
* it over the old one; if the second rename fails, the first file gets its old
|
|
92
|
+
* bytes back. A fresh create whose second file fails removes the first. Every
|
|
93
|
+
* path, temporary ones included, is checked to be a plain file directly inside
|
|
94
|
+
* the directory before anything is created.
|
|
95
|
+
*
|
|
96
|
+
* @throws InvalidPhaseIdentityError when `phase` cannot name a file.
|
|
97
|
+
* @throws the file system's own error when a write fails; nothing is left behind.
|
|
81
98
|
*/
|
|
82
|
-
export declare function
|
|
99
|
+
export declare function writeRecordPair(repoRoot: string, phase: string, rawRecord: unknown, record: BuildRecordV2, io?: RecordPairIo): RecordPairResult;
|
|
83
100
|
//# sourceMappingURL=write.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/build-record/write.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/build-record/write.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAcH,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,mBAAmB,CAAC;AAE3B,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,QAAgC,CAAC;AAE9D;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAM7F;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAClD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAsBD,eAAO,MAAM,mBAAmB,EAAE,YAGjC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,SAAS,GAAG,YAAY,GAAG,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACjF;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,CAEhE;AA0CD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,OAAO,EAClB,MAAM,EAAE,aAAa,EACrB,EAAE,GAAE,YAAkC,GACrC,gBAAgB,CAyDlB"}
|
package/dist/close-phase.d.ts
CHANGED
|
@@ -29,13 +29,40 @@
|
|
|
29
29
|
*
|
|
30
30
|
* INV-001: this file imports the global `fetch` (Node 20), Node builtins, and
|
|
31
31
|
* `@halfcycle/events` — one of the exactly two workspace packages a shippable may
|
|
32
|
-
* import. `
|
|
33
|
-
* (INV-003), so the CLI validates
|
|
34
|
-
* parses with. It never imports the control service.
|
|
32
|
+
* import. `acceptPhaseRequestSchema` is the ONE home for the request's shape
|
|
33
|
+
* (INV-003), so the CLI validates the whole body it builds against the same schema
|
|
34
|
+
* the route parses with. It never imports the control service.
|
|
35
|
+
*
|
|
36
|
+
* SINCE CONNECT PHASE 4 (`close-produces-the-record`) the close carries the
|
|
37
|
+
* client's statements and the full commit it closed at, and writes no local close
|
|
38
|
+
* note. The service freezes both into the phase's Build Record; the caller fetches
|
|
39
|
+
* that record afterwards (`build-record/obtain.ts`).
|
|
35
40
|
*/
|
|
36
41
|
import { type AcceptanceOutcome, type PhaseCloseDecision } from '@halfcycle/events';
|
|
37
42
|
import { type PhaseStampFailure, type RepoCredential } from './open-phase.js';
|
|
38
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Whether the commit this phase closed at could be read, and if so the full hash
|
|
45
|
+
* that travelled with the close. When it could not, `reason` says why in words a
|
|
46
|
+
* developer can act on; the record shows the commit as not stated.
|
|
47
|
+
*/
|
|
48
|
+
export type CloseCommitOutcome = {
|
|
49
|
+
read: true;
|
|
50
|
+
commit: string;
|
|
51
|
+
} | {
|
|
52
|
+
read: false;
|
|
53
|
+
reason: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* The client's own words on a close: what the phase set out to prove, what came of
|
|
57
|
+
* it, and how the work departed from the plan. `null` / empty when not stated.
|
|
58
|
+
*/
|
|
59
|
+
export interface StatedStatements {
|
|
60
|
+
thesis: string | null;
|
|
61
|
+
outcome: string | null;
|
|
62
|
+
deviations: string[];
|
|
63
|
+
}
|
|
64
|
+
/** Nothing stated — what a close sends when no statement flag was given. */
|
|
65
|
+
export declare const NO_STATEMENTS: StatedStatements;
|
|
39
66
|
/** What `POST /engagements/:id/accept` returns on 200 (the fields the CLI reports). */
|
|
40
67
|
export interface ClosedPhase {
|
|
41
68
|
engagementId: string;
|
|
@@ -47,13 +74,20 @@ export interface ClosedPhase {
|
|
|
47
74
|
* machine is still filed under a phase that is no longer open.
|
|
48
75
|
*/
|
|
49
76
|
stampFailure: PhaseStampFailure | null;
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
* and where. When it was not, this says why, and the phase's Build Record will
|
|
53
|
-
* say the commit and the close date were not recorded.
|
|
54
|
-
*/
|
|
55
|
-
closeRecord: CloseRecordOutcome;
|
|
77
|
+
/** The commit sent with the close, or why none could be read. */
|
|
78
|
+
closeCommit: CloseCommitOutcome;
|
|
56
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Read the full HEAD commit of the NAMED repository, before the close is sent.
|
|
82
|
+
*
|
|
83
|
+
* `gitEnvForNamedRepo` strips the variables that tell git which repository to use,
|
|
84
|
+
* so an absolute `GIT_DIR` inherited from a hook cannot substitute another
|
|
85
|
+
* repository's HEAD for this one's. git's own stderr is discarded: a failure here
|
|
86
|
+
* is expected, the close still goes ahead, and the caller says why in plain words.
|
|
87
|
+
* The hash is checked by the contract's own `closeCommitSchema`, so a short or
|
|
88
|
+
* malformed answer is never sent.
|
|
89
|
+
*/
|
|
90
|
+
export declare function readCloseCommit(repoRoot: string | undefined): CloseCommitOutcome;
|
|
57
91
|
/**
|
|
58
92
|
* A refusal from the control plane, carried as an error the CLI can print whole.
|
|
59
93
|
*
|
|
@@ -108,6 +142,13 @@ export declare class ClosePhaseRefused extends Error {
|
|
|
108
142
|
*/
|
|
109
143
|
code?: string | undefined);
|
|
110
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* A close refused on this machine, before anything was sent: the request failed the
|
|
147
|
+
* shared request schema. The CLI prints the message and exits as a usage error.
|
|
148
|
+
*/
|
|
149
|
+
export declare class CloseRequestInvalid extends Error {
|
|
150
|
+
constructor(message: string);
|
|
151
|
+
}
|
|
111
152
|
/**
|
|
112
153
|
* Record a phase's acceptance and close it.
|
|
113
154
|
*
|
|
@@ -118,16 +159,18 @@ export declare class ClosePhaseRefused extends Error {
|
|
|
118
159
|
* for the same reason `openPhase` requires its entry decision: there
|
|
119
160
|
* is no argument-less way to move the phase axis, so an unattributed
|
|
120
161
|
* close is unreachable from any caller that compiles.
|
|
121
|
-
* @param repoRoot the repository whose
|
|
162
|
+
* @param repoRoot the repository whose HEAD this close is sent with.
|
|
122
163
|
* Optional so that a caller with no repository in hand — a test
|
|
123
164
|
* harness, a future non-repo surface — is not forced to invent one;
|
|
124
|
-
* omitting it
|
|
125
|
-
* process's working directory, which would
|
|
126
|
-
*
|
|
165
|
+
* omitting it sends no commit rather than silently resolving the
|
|
166
|
+
* process's working directory, which would send a stranger's HEAD.
|
|
167
|
+
* @param stated the client's own statements; each absent one is sent as not
|
|
168
|
+
* stated (`null`, or no deviations).
|
|
127
169
|
*
|
|
128
170
|
* @throws ClosePhaseRefused on a 4xx the plane explained, so the CLI prints the
|
|
129
171
|
* plane's own sentence and exits non-zero.
|
|
130
|
-
* @throws Error
|
|
172
|
+
* @throws Error before anything is sent when the request fails the shared schema;
|
|
173
|
+
* on transport failure; or on a body that is not the expected shape.
|
|
131
174
|
*/
|
|
132
|
-
export declare function closePhase(credential: RepoCredential, phase: string, outcome: AcceptanceOutcome, close: PhaseCloseDecision, home?: string, repoRoot?: string): Promise<ClosedPhase>;
|
|
175
|
+
export declare function closePhase(credential: RepoCredential, phase: string, outcome: AcceptanceOutcome, close: PhaseCloseDecision, home?: string, repoRoot?: string, stated?: StatedStatements): Promise<ClosedPhase>;
|
|
133
176
|
//# sourceMappingURL=close-phase.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"close-phase.d.ts","sourceRoot":"","sources":["../src/close-phase.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"close-phase.d.ts","sourceRoot":"","sources":["../src/close-phase.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAGH,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAmB,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAG/F;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElG;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,4EAA4E;AAC5E,eAAO,MAAM,aAAa,EAAE,gBAAkE,CAAC;AAE/F,uFAAuF;AACvF,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,YAAY,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACvC,iEAAiE;IACjE,WAAW,EAAE,kBAAkB,CAAC;CACjC;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,kBAAkB,CA4BhF;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;aAExB,MAAM,EAAE,MAAM;IAE9B;;;;;OAKG;aACa,OAAO,CAAC,EAAE,MAAM;IAChC;;;;;;;;;;OAUG;aACa,IAAI,CAAC,EAAE,MAAM;gBApBb,MAAM,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM;IACf;;;;;OAKG;IACa,OAAO,CAAC,EAAE,MAAM,YAAA;IAChC;;;;;;;;;;OAUG;IACa,IAAI,CAAC,EAAE,MAAM,YAAA;CAKhC;AAED;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,UAAU,CAC9B,UAAU,EAAE,cAAc,EAC1B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,iBAAiB,EAC1B,KAAK,EAAE,kBAAkB,EACzB,IAAI,CAAC,EAAE,MAAM,EACb,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,GAAE,gBAAgC,GACvC,OAAO,CAAC,WAAW,CAAC,CAyEtB"}
|