halfcycle 0.3.17 → 0.3.19
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/README.md +1 -1
- package/bin/bin.bundle.mjs +224 -33
- package/dist/bin.js +571 -198
- package/dist/bin.js.map +3 -3
- package/dist/build-record/close-record.d.ts +124 -0
- package/dist/build-record/close-record.d.ts.map +1 -0
- package/dist/build-record/index.d.ts +8 -5
- package/dist/build-record/index.d.ts.map +1 -1
- package/dist/build-record/sources.d.ts +49 -18
- package/dist/build-record/sources.d.ts.map +1 -1
- package/dist/build-record/template.d.ts.map +1 -1
- package/dist/build-record/types.d.ts +49 -12
- package/dist/build-record/types.d.ts.map +1 -1
- package/dist/build-record/write.d.ts +60 -3
- package/dist/build-record/write.d.ts.map +1 -1
- package/dist/close-phase.d.ts +21 -2
- package/dist/close-phase.d.ts.map +1 -1
- package/dist/confirm-identity.d.ts +25 -0
- package/dist/confirm-identity.d.ts.map +1 -1
- package/dist/create-engagement.d.ts +29 -1
- package/dist/create-engagement.d.ts.map +1 -1
- package/dist/engagement-credential.d.ts +85 -2
- package/dist/engagement-credential.d.ts.map +1 -1
- package/dist/index.js +412 -142
- package/dist/index.js.map +3 -3
- package/dist/install.d.ts.map +1 -1
- package/dist/open-phase.d.ts +58 -1
- package/dist/open-phase.d.ts.map +1 -1
- package/dist/own-engagement.d.ts +50 -10
- package/dist/own-engagement.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Source loading + fail-loud validation for the Build-Record assembler.
|
|
3
3
|
*
|
|
4
|
-
* INV-001 (local by construction): every source is a LOCAL file
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* INV-001 (local by construction): every source is a LOCAL file. The guard-eval
|
|
5
|
+
* log is a phase-scoped JSONL file the runner wrote locally (E2-T-03b) and the
|
|
6
|
+
* close commit/date come from the phase's own close record — never a hosted
|
|
7
|
+
* store call, and since `phase-close-commit` never a live git call either. This
|
|
8
|
+
* module imports only @halfcycle/events (for the wire schema), Node builtins and
|
|
9
|
+
* its own package.
|
|
8
10
|
*
|
|
9
11
|
* INV-007: the guard-eval log is parsed through `guardEvalRunSchema` from
|
|
10
12
|
* @halfcycle/events, so only the FiredGuard-safe fields survive. A line
|
|
@@ -14,8 +16,8 @@
|
|
|
14
16
|
* Fail-loud (AC2): a missing required source throws `MissingSourceError` naming
|
|
15
17
|
* the input. Nothing is blanked or invented.
|
|
16
18
|
*/
|
|
17
|
-
import { type GuardEvalRun } from '@halfcycle/events';
|
|
18
|
-
import type { BuildRecordSources,
|
|
19
|
+
import { type GuardEvalRun, type PhaseIdentity } from '@halfcycle/events';
|
|
20
|
+
import type { BuildRecordSources, NarratedSource, OrchestrationStateSource, PhaseCloseSource } from './types.js';
|
|
19
21
|
import { type SeedGuard } from './types.js';
|
|
20
22
|
/** Thrown when a required source artefact is missing — fail loud, never blank. */
|
|
21
23
|
export declare class MissingSourceError extends Error {
|
|
@@ -39,41 +41,70 @@ export declare class MissingSourceError extends Error {
|
|
|
39
41
|
*/
|
|
40
42
|
export declare function projectSeedGuards(runs: GuardEvalRun[]): SeedGuard[];
|
|
41
43
|
/**
|
|
42
|
-
* Read + parse the phase-scoped guard-eval log file
|
|
44
|
+
* Read + parse the phase-scoped guard-eval log file, whose name comes from
|
|
45
|
+
* `renderPhaseSegment` — the same renderer the writer uses, so the reader cannot
|
|
46
|
+
* drift from it by inspection. The identity is a name or a number; an unsafe one
|
|
47
|
+
* is refused by the renderer before any path is joined.
|
|
48
|
+
*
|
|
43
49
|
* Every line is legacy-normalised (see `normalizeLegacyLine`) then validated
|
|
44
50
|
* through the strict `guardEvalRunSchema` — a mixed-era log (pre-amendment lines
|
|
45
51
|
* with no `runId`/`outcome`, plus new-era `evaluated`/failure/`unconfigured`
|
|
46
52
|
* lines) assembles cleanly, while an undeclared field still rejects. Fails loud
|
|
47
53
|
* when the log file is absent (a required source per the field→source map).
|
|
48
54
|
*/
|
|
49
|
-
export declare function readGuardEvalLog(logDir: string, phaseId:
|
|
50
|
-
/** Load the orchestration-state task map; fail loud if the file is absent. */
|
|
51
|
-
export declare function readOrchestrationState(phasesDir: string, phaseId: number): OrchestrationStateSource;
|
|
55
|
+
export declare function readGuardEvalLog(logDir: string, phaseId: PhaseIdentity): GuardEvalRun[];
|
|
52
56
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
57
|
+
* Load the orchestration-state task map; fail loud if the file is absent.
|
|
58
|
+
* The filename is produced by `renderPhaseSegment`, so this reader and the
|
|
59
|
+
* Build-Record writer name a phase the same way by construction.
|
|
56
60
|
*/
|
|
57
|
-
export declare function
|
|
61
|
+
export declare function readOrchestrationState(phasesDir: string, phaseId: PhaseIdentity): OrchestrationStateSource;
|
|
62
|
+
/**
|
|
63
|
+
* Read what the phase's close recorded about the repository.
|
|
64
|
+
*
|
|
65
|
+
* THIS REPLACED A GIT CALL, and the replacement is the feature. The assembler
|
|
66
|
+
* used to run `git rev-parse --short HEAD` here, at ASSEMBLY time, so the commit
|
|
67
|
+
* and date a record named were a function of when someone happened to assemble
|
|
68
|
+
* it — close on Monday, assemble on Friday, and the record described Friday
|
|
69
|
+
* while claiming to describe the close. Nothing re-derives either value now.
|
|
70
|
+
*
|
|
71
|
+
* The filename comes from `closeRecordPath` — the same function the close-side
|
|
72
|
+
* writer calls, which builds it through the same renderer every other
|
|
73
|
+
* phase-scoped filename here goes through. Writer and reader cannot drift.
|
|
74
|
+
*
|
|
75
|
+
* AN ABSENT FILE IS ABSENCE, not a failure, and it is the one required-source
|
|
76
|
+
* reader here that does not fail loud. Closing a phase has never needed git, so
|
|
77
|
+
* a close that recorded nothing is legitimate — as is a close made before this
|
|
78
|
+
* file existed, whose commit is simply unrecoverable. Both read as `null` and
|
|
79
|
+
* the record says so. A PRESENT file that cannot be trusted still fails loud,
|
|
80
|
+
* because that is a broken input rather than an absent one.
|
|
81
|
+
*/
|
|
82
|
+
export declare function readPhaseClose(repoRoot: string, phaseId: PhaseIdentity): PhaseCloseSource;
|
|
58
83
|
/**
|
|
59
84
|
* Validate that a caller-supplied narrated bundle has every required field.
|
|
60
85
|
* The narrated fields are lifted from prose artefacts (phase doc, acceptance
|
|
61
86
|
* report, COE notes, ENGAGEMENT ledger) by the loader; this guard makes the
|
|
62
87
|
* "missing source fails loud" contract hold for them too.
|
|
88
|
+
*
|
|
89
|
+
* It also refuses an unsafe `phase.id` (INV-023). That field is not merely data:
|
|
90
|
+
* it is copied into the assembled record and interpolated into the record's own
|
|
91
|
+
* output filename, so it is the one narrated field that decides where the record
|
|
92
|
+
* is written. It is validated here because this is where every route converges.
|
|
63
93
|
*/
|
|
64
94
|
export declare function validateNarrated(narrated: NarratedSource): NarratedSource;
|
|
65
95
|
/**
|
|
66
96
|
* Assemble the complete `BuildRecordSources` bundle from a phase's local
|
|
67
97
|
* artefacts. This is the local-by-construction (INV-001) loader: it reads the
|
|
68
|
-
* orchestration state, the runner's local guard-eval log,
|
|
69
|
-
* caller-lifted narrated + touched-invariants inputs. Every
|
|
70
|
-
* present or it throws
|
|
98
|
+
* orchestration state, the runner's local guard-eval log, the phase's own close
|
|
99
|
+
* record, and the caller-lifted narrated + touched-invariants inputs. Every
|
|
100
|
+
* required source is present or it throws — with one deliberate exception named
|
|
101
|
+
* at `readPhaseClose`, whose absence is a legitimate answer rather than a gap.
|
|
71
102
|
*/
|
|
72
103
|
export declare function loadSources(opts: {
|
|
73
104
|
repoRoot: string;
|
|
74
105
|
phasesDir: string;
|
|
75
106
|
guardEvalLogDir: string;
|
|
76
|
-
phaseId:
|
|
107
|
+
phaseId: PhaseIdentity;
|
|
77
108
|
narrated: NarratedSource;
|
|
78
109
|
touchedInvariants: string[];
|
|
79
110
|
}): BuildRecordSources;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sources.d.ts","sourceRoot":"","sources":["../../src/build-record/sources.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sources.d.ts","sourceRoot":"","sources":["../../src/build-record/sources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,aAAa,EACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EACV,kBAAkB,EAClB,cAAc,EACd,wBAAwB,EACxB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAI9D,kFAAkF;AAClF,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAI/C;AAMD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,SAAS,EAAE,CAwBnE;AAwED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,YAAY,EAAE,CAoBvF;AAMD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,GACrB,wBAAwB,CAsB1B;AAMD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,gBAAgB,CAyBzF;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,cAAc,GAAG,cAAc,CA2BzE;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,cAAc,CAAC;IACzB,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B,GAAG,kBAAkB,CAerB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/build-record/template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/build-record/template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,YAAY,CAAC;AAsD3D;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAiFvE"}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* `severity`, optional per-firing `explanation`). It is the ONLY guard-derived
|
|
13
13
|
* shape in the record. There is no path from a `GuardRecord` to this file.
|
|
14
14
|
*/
|
|
15
|
-
import type { FiredGuard, GuardEvalRun } from '@halfcycle/events';
|
|
15
|
+
import type { FiredGuard, GuardEvalRun, PhaseIdentity } from '@halfcycle/events';
|
|
16
16
|
/** The v1 format string — a single literal, one home. */
|
|
17
17
|
export declare const BUILD_RECORD_FORMAT: "halfcycle-build-record/v1";
|
|
18
18
|
/**
|
|
@@ -81,18 +81,40 @@ export interface Tasks {
|
|
|
81
81
|
landed: number;
|
|
82
82
|
cancelled: string[];
|
|
83
83
|
}
|
|
84
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* The v1 Build Record — the JSON companion. Field order matters for the seed.
|
|
86
|
+
*
|
|
87
|
+
* `phase.id` is a `PhaseIdentity` — a name OR a number — because a phase is
|
|
88
|
+
* identified by whatever the client called it, and three of this repo's own
|
|
89
|
+
* phases are numbered while twenty-four are named. The widening is deliberately
|
|
90
|
+
* NOT a narrowing to `string`: `docs/build-records/phase-0.json` carries the
|
|
91
|
+
* JSON number `0`, the assembler copies the identity through untransformed, and
|
|
92
|
+
* `packages/bundle/test/build-record-phase0.test.ts` deep-equals the two. Under
|
|
93
|
+
* the union that conformance seed passes with no fixture edit; under `string`
|
|
94
|
+
* alone it would have needed a re-conform and a second operator approval.
|
|
95
|
+
*
|
|
96
|
+
* `closeCommit` and `closedDate` ADMIT ABSENCE, which is the v1 shape moving
|
|
97
|
+
* rather than a field being added. Both were required strings, on the premise
|
|
98
|
+
* that a record could always be given them because they were derived from git at
|
|
99
|
+
* assembly time. They are now captured at the close, and a client with no git
|
|
100
|
+
* closes successfully — so the pair has a legitimate "not recorded" state, and a
|
|
101
|
+
* required string could only have been satisfied by inventing one or by
|
|
102
|
+
* refusing to assemble the record at all. The conformance seed carries strings
|
|
103
|
+
* in both and is unaffected: widening a field admits the value it already held.
|
|
104
|
+
*/
|
|
85
105
|
export interface BuildRecordV1 {
|
|
86
106
|
format: typeof BUILD_RECORD_FORMAT;
|
|
87
107
|
engagement: string;
|
|
88
108
|
engagementType: string;
|
|
89
109
|
phase: {
|
|
90
|
-
id:
|
|
110
|
+
id: PhaseIdentity;
|
|
91
111
|
name: string;
|
|
92
112
|
};
|
|
93
113
|
status: string;
|
|
94
|
-
|
|
95
|
-
|
|
114
|
+
/** The date the phase closed, or null when the close recorded none. */
|
|
115
|
+
closedDate: string | null;
|
|
116
|
+
/** The commit the phase closed at, or null when the close recorded none. */
|
|
117
|
+
closeCommit: string | null;
|
|
96
118
|
thesis: string;
|
|
97
119
|
thesisHeld: boolean;
|
|
98
120
|
delivered: Delivered;
|
|
@@ -106,7 +128,8 @@ export interface BuildRecordV1 {
|
|
|
106
128
|
}
|
|
107
129
|
/**
|
|
108
130
|
* Orchestration-state projection: task terminal states + engagement identity.
|
|
109
|
-
* Loaded from
|
|
131
|
+
* Loaded from the phase-scoped orchestration-state file, whose name is produced
|
|
132
|
+
* by `renderPhaseSegment` and never spelled out here (Zone-B process artefact).
|
|
110
133
|
* Terminal states: `merged`/`done` → landed; `cancelled` → cancelled.
|
|
111
134
|
*/
|
|
112
135
|
export interface OrchestrationStateSource {
|
|
@@ -127,7 +150,7 @@ export interface NarratedSource {
|
|
|
127
150
|
engagement: string;
|
|
128
151
|
engagementType: string;
|
|
129
152
|
phase: {
|
|
130
|
-
id:
|
|
153
|
+
id: PhaseIdentity;
|
|
131
154
|
name: string;
|
|
132
155
|
};
|
|
133
156
|
status: string;
|
|
@@ -138,10 +161,24 @@ export interface NarratedSource {
|
|
|
138
161
|
instruments: Instruments;
|
|
139
162
|
deviations: string[];
|
|
140
163
|
}
|
|
141
|
-
/**
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
164
|
+
/**
|
|
165
|
+
* What the phase's close recorded about the repository.
|
|
166
|
+
*
|
|
167
|
+
* NOT resolved at assembly time, which is the change this type carries. Both
|
|
168
|
+
* values are captured when the phase closes and read back from the local close
|
|
169
|
+
* record (`close-record.ts`), so assembling the same phase twice returns the
|
|
170
|
+
* same document however much work has happened since.
|
|
171
|
+
*
|
|
172
|
+
* `null` on both is a close that recorded neither — a client with no git, no
|
|
173
|
+
* repository or no commit yet closes successfully and this reads as absence
|
|
174
|
+
* rather than as a blank or a value derived later. The pair moves together on
|
|
175
|
+
* both paths, so one is never null while the other is a string.
|
|
176
|
+
*/
|
|
177
|
+
export interface PhaseCloseSource {
|
|
178
|
+
/** The commit the repository was on when the phase closed, or null if none was recorded. */
|
|
179
|
+
closeCommit: string | null;
|
|
180
|
+
/** The date of that commit, or null if none was recorded. */
|
|
181
|
+
closedDate: string | null;
|
|
145
182
|
}
|
|
146
183
|
/**
|
|
147
184
|
* The complete typed source bundle. Every required source is present or the
|
|
@@ -151,7 +188,7 @@ export interface GitSource {
|
|
|
151
188
|
export interface BuildRecordSources {
|
|
152
189
|
orchestration: OrchestrationStateSource;
|
|
153
190
|
narrated: NarratedSource;
|
|
154
|
-
|
|
191
|
+
close: PhaseCloseSource;
|
|
155
192
|
/** The runner's LOCAL guard-eval log for the phase (INV-007-safe wire records). */
|
|
156
193
|
guardEvalRuns: GuardEvalRun[];
|
|
157
194
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/build-record/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/build-record/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,8 +2,50 @@
|
|
|
2
2
|
* Write-disciplined Build-Record writer.
|
|
3
3
|
*
|
|
4
4
|
* Blast radius: the writer touches ONLY `docs/build-records/` in the target
|
|
5
|
-
* repo (same write-discipline as the bundle installer, E1-T-06)
|
|
6
|
-
*
|
|
5
|
+
* repo (same write-discipline as the bundle installer, E1-T-06), and now
|
|
6
|
+
* enforces it rather than claiming it (INV-023).
|
|
7
|
+
*
|
|
8
|
+
* THE RESIDUAL CHECK, AND WHY IT IS NOT DEFENCE IN DEPTH. The identity is
|
|
9
|
+
* validated where every caller converges (`validateNarrated`), but
|
|
10
|
+
* `assembleBuildRecord` and `writeBuildRecord` are exported individually too, so
|
|
11
|
+
* a library caller can compose them without entering `loadSources` at all. For
|
|
12
|
+
* that caller the assertion below is the ONLY guard.
|
|
13
|
+
*
|
|
14
|
+
* It asserts on the joined OUTPUT PATH, not on the output directory. The
|
|
15
|
+
* directory is a constant and the filename is the caller-influenced half, so a
|
|
16
|
+
* directory comparison guards the half that was never at risk — which is exactly
|
|
17
|
+
* how this function's previous check came to compare a value with itself and
|
|
18
|
+
* refuse nothing. The two operands here are genuinely different: `outDir` is
|
|
19
|
+
* derived from `repoRoot` and a constant and does not move when the record
|
|
20
|
+
* changes, while the candidate path is derived from `record.phase.id` and moves
|
|
21
|
+
* with every record.
|
|
22
|
+
*
|
|
23
|
+
* WHAT REFUSES FIRST CHANGED WHEN THE FILENAME MOVED TO THE RENDERER, and saying
|
|
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.
|
|
7
49
|
*
|
|
8
50
|
* The JSON is serialised with a stable 2-space indent + trailing newline; the
|
|
9
51
|
* markdown data slots are projected from the same record object, so the pair
|
|
@@ -18,9 +60,24 @@ export interface WriteResult {
|
|
|
18
60
|
}
|
|
19
61
|
/** Serialise the record JSON deterministically (2-space indent + trailing NL). */
|
|
20
62
|
export declare function serialiseRecordJson(record: BuildRecordV1): string;
|
|
63
|
+
/**
|
|
64
|
+
* Assert that a joined output path is still a plain file directly inside
|
|
65
|
+
* `outDir`. `relative` is computed between two values that move independently —
|
|
66
|
+
* the allowed directory, and a candidate path built from the record — so this
|
|
67
|
+
* comparison can actually fail.
|
|
68
|
+
*
|
|
69
|
+
* EXPORTED SO IT CAN BE TESTED AT ALL. Its subject is a filename segment that
|
|
70
|
+
* grew a separator, which only an edit in another package can produce, so no
|
|
71
|
+
* behavioural test of this package can reach it — see the module header. It is
|
|
72
|
+
* called directly by `build-record-write-discipline.test.ts` with a candidate
|
|
73
|
+
* outside the directory, which is the only control that can tell this guard
|
|
74
|
+
* from a deleted one.
|
|
75
|
+
*/
|
|
76
|
+
export declare function assertInsideOutDir(outDir: string, candidate: string, identity: unknown): void;
|
|
21
77
|
/**
|
|
22
78
|
* Write the `.json` + `.md` pair for a phase into `docs/build-records/`.
|
|
23
|
-
* Refuses
|
|
79
|
+
* Refuses — writing nothing at all — if the record's identity would put either
|
|
80
|
+
* file anywhere else.
|
|
24
81
|
*/
|
|
25
82
|
export declare function writeBuildRecord(repoRoot: string, record: BuildRecordV1): WriteResult;
|
|
26
83
|
//# 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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIhD,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,QAAgC,CAAC;AAE9D,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAEjE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAM7F;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,WAAW,CA2BrF"}
|
package/dist/close-phase.d.ts
CHANGED
|
@@ -34,12 +34,25 @@
|
|
|
34
34
|
* parses with. It never imports the control service.
|
|
35
35
|
*/
|
|
36
36
|
import { type AcceptanceOutcome, type PhaseCloseDecision } from '@halfcycle/events';
|
|
37
|
-
import type
|
|
37
|
+
import { type PhaseStampFailure, type RepoCredential } from './open-phase.js';
|
|
38
|
+
import { type CloseRecordOutcome } from './build-record/close-record.js';
|
|
38
39
|
/** What `POST /engagements/:id/accept` returns on 200 (the fields the CLI reports). */
|
|
39
40
|
export interface ClosedPhase {
|
|
40
41
|
engagementId: string;
|
|
41
42
|
/** The lifecycle status after the close — `closed`. */
|
|
42
43
|
status: string;
|
|
44
|
+
/**
|
|
45
|
+
* Set when the phase closed but this machine's record of it could not be
|
|
46
|
+
* cleared. `null` on every ordinary success. Until it is cleared, work on this
|
|
47
|
+
* machine is still filed under a phase that is no longer open.
|
|
48
|
+
*/
|
|
49
|
+
stampFailure: PhaseStampFailure | null;
|
|
50
|
+
/**
|
|
51
|
+
* Whether the commit this phase closed at was written down on this machine,
|
|
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;
|
|
43
56
|
}
|
|
44
57
|
/**
|
|
45
58
|
* A refusal from the control plane, carried as an error the CLI can print whole.
|
|
@@ -105,10 +118,16 @@ export declare class ClosePhaseRefused extends Error {
|
|
|
105
118
|
* for the same reason `openPhase` requires its entry decision: there
|
|
106
119
|
* is no argument-less way to move the phase axis, so an unattributed
|
|
107
120
|
* close is unreachable from any caller that compiles.
|
|
121
|
+
* @param repoRoot the repository whose commit this close is being recorded against.
|
|
122
|
+
* Optional so that a caller with no repository in hand — a test
|
|
123
|
+
* harness, a future non-repo surface — is not forced to invent one;
|
|
124
|
+
* omitting it records no commit rather than silently resolving the
|
|
125
|
+
* process's working directory, which would write a stranger's HEAD
|
|
126
|
+
* into this phase's record.
|
|
108
127
|
*
|
|
109
128
|
* @throws ClosePhaseRefused on a 4xx the plane explained, so the CLI prints the
|
|
110
129
|
* plane's own sentence and exits non-zero.
|
|
111
130
|
* @throws Error on transport failure or a body that is not the expected shape.
|
|
112
131
|
*/
|
|
113
|
-
export declare function closePhase(credential: RepoCredential, phase: string, outcome: AcceptanceOutcome, close: PhaseCloseDecision): Promise<ClosedPhase>;
|
|
132
|
+
export declare function closePhase(credential: RepoCredential, phase: string, outcome: AcceptanceOutcome, close: PhaseCloseDecision, home?: string, repoRoot?: string): Promise<ClosedPhase>;
|
|
114
133
|
//# 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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"close-phase.d.ts","sourceRoot":"","sources":["../src/close-phase.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAmB,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC/F,OAAO,EAAoB,KAAK,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAE3F,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;;;;OAIG;IACH,WAAW,EAAE,kBAAkB,CAAC;CACjC;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;;;;;;;;;;;;;;;;;;;;GAoBG;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,GAChB,OAAO,CAAC,WAAW,CAAC,CAwFtB"}
|
|
@@ -187,6 +187,31 @@ export declare function accountLabel(identity: AccountIdentity | undefined, stor
|
|
|
187
187
|
* confirm an account nobody read.
|
|
188
188
|
*/
|
|
189
189
|
export declare function readDecision(answer: string): IdentityDecision;
|
|
190
|
+
/**
|
|
191
|
+
* How to act as a DIFFERENT account on the NEXT run of this CLI — three forms, and
|
|
192
|
+
* every one of them is EXECUTABLE FROM THE STATE THIS PRINTS IN
|
|
193
|
+
* (`solo-project-ownership`).
|
|
194
|
+
*
|
|
195
|
+
* WHY THIS IS NOT COMPOSED AT THE CALL SITE THAT NEEDS IT. The not-this-account join
|
|
196
|
+
* refusal (`own-engagement.ts`) fires AFTER `confirmActingIdentity` has already run
|
|
197
|
+
* for this install — the reader has already passed the identity prompt above (or
|
|
198
|
+
* skipped it, on an arm that does not ask) and this account is the one that just got
|
|
199
|
+
* refused. "Sign in as the owner" alone is a remedy that cannot be executed from
|
|
200
|
+
* where it would be printed; what CAN be executed is what to do on the run after this
|
|
201
|
+
* one, and that is exactly the three forms this module already uses elsewhere to let
|
|
202
|
+
* a person switch identity: answer "s" when the prompt above appears again, set
|
|
203
|
+
* `HALFCYCLE_TOKEN` for the account that owns the project (it wins over anything
|
|
204
|
+
* saved), or remove this machine's saved sign-in so the next run asks fresh.
|
|
205
|
+
*
|
|
206
|
+
* ONE HOME, so a caller outside this module states the same fact rather than
|
|
207
|
+
* retyping it — the paraphrase defect this repository has already paid for on the
|
|
208
|
+
* plane's own side of this feature (a control written against a retyped copy of a
|
|
209
|
+
* message agrees with itself and with nothing).
|
|
210
|
+
*
|
|
211
|
+
* `home` is the per-user store's directory, exactly as `accountStorePath` takes it —
|
|
212
|
+
* `undefined` defaults to `os.homedir()`.
|
|
213
|
+
*/
|
|
214
|
+
export declare function identitySwitchRemedy(home?: string): string;
|
|
190
215
|
/**
|
|
191
216
|
* Name the account this run is about to act as, and — when a person is there to answer
|
|
192
217
|
* — let them stop it or sign in as somebody else.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"confirm-identity.d.ts","sourceRoot":"","sources":["../src/confirm-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkGG;AAGH,OAAO,EAA4B,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAInF,OAAO,EAAiB,KAAK,cAAc,EAAE,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAKtG,uFAAuF;AACvF,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AAErD,6CAA6C;AAC7C,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,gBAAgB,GAAG,MAAM,CAAC;AAErE,6FAA6F;AAC7F,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1F,MAAM,WAAW,mBAAoB,SAAQ,cAAc;IACzD,8DAA8D;IAC9D,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,gFAAgF;IAChF,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iEAAiE;IACjE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,oFAAoF;IACpF,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAiBtC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,eAAe,GAAG,SAAS,EACrC,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,OAAO,UAAQ,GACd,MAAM,CASR;AA+BD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAK7D;AAyJD;;;;;;;;;;GAUG;AACH,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,kBAAkB,EAC9B,GAAG,EAAE,UAAU,EACf,IAAI,GAAE,mBAAwB,GAC7B,OAAO,CAAC,kBAAkB,CAAC,CAuD7B"}
|
|
1
|
+
{"version":3,"file":"confirm-identity.d.ts","sourceRoot":"","sources":["../src/confirm-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkGG;AAGH,OAAO,EAA4B,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAInF,OAAO,EAAiB,KAAK,cAAc,EAAE,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAKtG,uFAAuF;AACvF,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AAErD,6CAA6C;AAC7C,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,gBAAgB,GAAG,MAAM,CAAC;AAErE,6FAA6F;AAC7F,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1F,MAAM,WAAW,mBAAoB,SAAQ,cAAc;IACzD,8DAA8D;IAC9D,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,gFAAgF;IAChF,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iEAAiE;IACjE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,oFAAoF;IACpF,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAiBtC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,eAAe,GAAG,SAAS,EACrC,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,OAAO,UAAQ,GACd,MAAM,CASR;AA+BD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAK7D;AAyJD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAM1D;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,kBAAkB,EAC9B,GAAG,EAAE,UAAU,EACf,IAAI,GAAE,mBAAwB,GAC7B,OAAO,CAAC,kBAAkB,CAAC,CAuD7B"}
|
|
@@ -150,13 +150,41 @@ export declare class CreateEngagementRefused extends Error {
|
|
|
150
150
|
readonly serverMessage: string;
|
|
151
151
|
/** The `error` discriminant, when the envelope carried one. */
|
|
152
152
|
readonly errorCode: string | undefined;
|
|
153
|
+
/**
|
|
154
|
+
* The refusal body's `reason` field, forwarded verbatim when the plane
|
|
155
|
+
* declared one — today, only the join route's not-this-account arm
|
|
156
|
+
* (`notThisAccountRefusalSchema`, T-05). `undefined` on every other
|
|
157
|
+
* refusal this class carries, including every create refusal, whose
|
|
158
|
+
* shape declares no such field at all.
|
|
159
|
+
*
|
|
160
|
+
* CARRIED HERE, INTERPRETED AT THE CALL SITE. This class is shared by
|
|
161
|
+
* both `createEngagement` and `joinEngagement`, and `reason`'s one
|
|
162
|
+
* meaning belongs to join. Branching on it lives where the two
|
|
163
|
+
* declared shapes are told apart — `own-engagement.ts`'s
|
|
164
|
+
* `isNotThisAccountRefusal` — not in this shared wiring.
|
|
165
|
+
*/
|
|
166
|
+
readonly reason: string | undefined;
|
|
153
167
|
constructor(
|
|
154
168
|
/** The HTTP status control answered with. */
|
|
155
169
|
status: number,
|
|
156
170
|
/** Control's own client-safe sentence, forwarded verbatim. */
|
|
157
171
|
serverMessage: string,
|
|
158
172
|
/** The `error` discriminant, when the envelope carried one. */
|
|
159
|
-
errorCode: string | undefined,
|
|
173
|
+
errorCode: string | undefined,
|
|
174
|
+
/**
|
|
175
|
+
* The refusal body's `reason` field, forwarded verbatim when the plane
|
|
176
|
+
* declared one — today, only the join route's not-this-account arm
|
|
177
|
+
* (`notThisAccountRefusalSchema`, T-05). `undefined` on every other
|
|
178
|
+
* refusal this class carries, including every create refusal, whose
|
|
179
|
+
* shape declares no such field at all.
|
|
180
|
+
*
|
|
181
|
+
* CARRIED HERE, INTERPRETED AT THE CALL SITE. This class is shared by
|
|
182
|
+
* both `createEngagement` and `joinEngagement`, and `reason`'s one
|
|
183
|
+
* meaning belongs to join. Branching on it lives where the two
|
|
184
|
+
* declared shapes are told apart — `own-engagement.ts`'s
|
|
185
|
+
* `isNotThisAccountRefusal` — not in this shared wiring.
|
|
186
|
+
*/
|
|
187
|
+
reason: string | undefined, message: string);
|
|
160
188
|
/**
|
|
161
189
|
* Is this a refusal of the CALLER's credential — the arm signing in again can fix?
|
|
162
190
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-engagement.d.ts","sourceRoot":"","sources":["../src/create-engagement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAIH;;oCAEoC;AACpC,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;OAWG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;;;;;;;;;OAkBG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IAE9C,6CAA6C;aAC7B,MAAM,EAAE,MAAM;IAC9B,8DAA8D;aAC9C,aAAa,EAAE,MAAM;IACrC,+DAA+D;aAC/C,SAAS,EAAE,MAAM,GAAG,SAAS;;
|
|
1
|
+
{"version":3,"file":"create-engagement.d.ts","sourceRoot":"","sources":["../src/create-engagement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAIH;;oCAEoC;AACpC,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;OAWG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;;;;;;;;;OAkBG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IAE9C,6CAA6C;aAC7B,MAAM,EAAE,MAAM;IAC9B,8DAA8D;aAC9C,aAAa,EAAE,MAAM;IACrC,+DAA+D;aAC/C,SAAS,EAAE,MAAM,GAAG,SAAS;IAC7C;;;;;;;;;;;;OAYG;aACa,MAAM,EAAE,MAAM,GAAG,SAAS;;IAnB1C,6CAA6C;IAC7B,MAAM,EAAE,MAAM;IAC9B,8DAA8D;IAC9C,aAAa,EAAE,MAAM;IACrC,+DAA+D;IAC/C,SAAS,EAAE,MAAM,GAAG,SAAS;IAC7C;;;;;;;;;;;;OAYG;IACa,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1C,OAAO,EAAE,MAAM;IAMjB;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,WAAW,IAAI,OAAO,CAGzB;CACF;AA4ED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,MAAM,EACb,UAAU,CAAC,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,iBAAiB,CAAC,CAU5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,iBAAiB,CAAC,CAW5B"}
|
|
@@ -101,6 +101,23 @@ export declare const PIN_ENGAGEMENT_ID_FIELD = "engagementId";
|
|
|
101
101
|
export declare function engagementStateDir(engagementId: string, home?: string): string;
|
|
102
102
|
/** `~/.halfcycle/engagements/<id>/env` — the owner-only per-engagement env file. */
|
|
103
103
|
export declare function engagementEnvPath(engagementId: string, home?: string): string;
|
|
104
|
+
/**
|
|
105
|
+
* `~/.halfcycle/engagements/<id>/not-this-account` — see
|
|
106
|
+
* `NOT_THIS_ACCOUNT_MARKER_FILENAME` above for why this is a third file rather than
|
|
107
|
+
* a field on either of the other two.
|
|
108
|
+
*/
|
|
109
|
+
export declare function notThisAccountMarkerPath(engagementId: string, home?: string): string;
|
|
110
|
+
/**
|
|
111
|
+
* Record that THIS MACHINE, as the account that just asked, was told the
|
|
112
|
+
* engagement belongs to somebody else — the write `install()` makes instead of a
|
|
113
|
+
* credential when the join route's not-this-account arm is what it met.
|
|
114
|
+
*
|
|
115
|
+
* Owner-only, same posture as the credential file it sits beside (phase decision
|
|
116
|
+
* 6) — it is a fact about this machine and this account, not a secret, but the
|
|
117
|
+
* posture is uniform across everything in this directory rather than decided file
|
|
118
|
+
* by file.
|
|
119
|
+
*/
|
|
120
|
+
export declare function writeNotThisAccountMarker(engagementId: string, home?: string): void;
|
|
104
121
|
/**
|
|
105
122
|
* The three variables the generated guard hook requires — **the bundle's one home
|
|
106
123
|
* for those names.** Anything in this package that needs to name them (the write
|
|
@@ -181,6 +198,25 @@ export declare const GUARD_COVERAGE_REQUIRED_KEYS: ("GUARD_SERVICE_TOKEN" | "GUA
|
|
|
181
198
|
*/
|
|
182
199
|
export declare const ENGAGEMENT_ENV_KEYS: readonly ["HALFCYCLE_SERVICE_URL", "HALFCYCLE_MCP_URL", "HALFCYCLE_TOKEN", "HALFCYCLE_ENGAGEMENT_ID", "GUARD_SERVICE_URL", "GUARD_SERVICE_TOKEN", "GUARD_ENGAGEMENT_ID", "CONTROL_TELEMETRY_URL"];
|
|
183
200
|
export type EngagementEnvKey = (typeof ENGAGEMENT_ENV_KEYS)[number];
|
|
201
|
+
/**
|
|
202
|
+
* The phase stamp — the one key in this file that is NOT a credential, named once
|
|
203
|
+
* here so the two verbs that move it (`open-phase.ts`, `close-phase.ts`) and this
|
|
204
|
+
* writer agree on the spelling.
|
|
205
|
+
*
|
|
206
|
+
* **Deliberately absent from `ENGAGEMENT_ENV_KEYS` above**, and that is the whole
|
|
207
|
+
* reason `reconcileEnvText`/`writeEngagementEnv` take a `keys` argument. It is not
|
|
208
|
+
* a secret, it is not written by the installer, and an install must not blank it:
|
|
209
|
+
* a re-install re-writes the eight credential keys and this key's line has to
|
|
210
|
+
* survive that untouched, which it does precisely because it is not in the list.
|
|
211
|
+
* The runner reads it out of this file (via the wrapper that sources it) to stamp
|
|
212
|
+
* the phase on every evaluation.
|
|
213
|
+
*
|
|
214
|
+
* `packages/mac-workspace-provider` writes the same name into the same file for a
|
|
215
|
+
* Studio-managed engagement (its own whole-file writer, INV-001-unreachable from
|
|
216
|
+
* here). Nothing is shared: this is the Connect writer, and the two topologies
|
|
217
|
+
* never run against one engagement.
|
|
218
|
+
*/
|
|
219
|
+
export declare const PHASE_STAMP_ENV_KEY = "HALFCYCLE_PHASE";
|
|
184
220
|
/**
|
|
185
221
|
* Parse `KEY=value` lines. **PARSES, NEVER SOURCES** — a credential store is not
|
|
186
222
|
* code, and `.` would execute whatever the file happens to contain. An optional
|
|
@@ -231,8 +267,31 @@ export declare function parseEnvText(raw: string): Record<string, string>;
|
|
|
231
267
|
* install, not preserved as a foreign writer's key. A Studio-written value for it is
|
|
232
268
|
* legitimately overwritten on the next Connect install; only `CONTROL_TELEMETRY_TOKEN`
|
|
233
269
|
* is still genuinely foreign.
|
|
270
|
+
*
|
|
271
|
+
* **A `null` VALUE REMOVES THE KEY'S LINE. An absent one still writes `KEY=''`.**
|
|
272
|
+
* The two are different requests and this function had no way to make the first
|
|
273
|
+
* one: `values[k] ?? ''` mapped both to the empty string, so the only reachable
|
|
274
|
+
* way to "unset" a key was to write it blank. Blank is not absent to any reader of
|
|
275
|
+
* this file — the shell that SOURCES it sets the variable to the empty string, and
|
|
276
|
+
* the Build-Record write predicate refuses an empty phase identity, so a phase
|
|
277
|
+
* stamp cleared by blanking would fire a refusal on the ordinary project-scope
|
|
278
|
+
* return. `open-phase --none` and `close-phase` therefore pass `null` and the
|
|
279
|
+
* assignment leaves the file wherever it sat, `export ` prefix and all. Nothing is
|
|
280
|
+
* appended for a removed key.
|
|
281
|
+
*
|
|
282
|
+
* **The header block is appended AT MOST ONCE per file**, and that follows from the
|
|
283
|
+
* same pair of verbs: `HALFCYCLE_PHASE` leaves on every close and comes back on
|
|
284
|
+
* every open, so a header emitted with each re-appearance would grow the file by a
|
|
285
|
+
* comment block per phase. A file that already carries the header gets the new
|
|
286
|
+
* lines under the one it has.
|
|
287
|
+
*
|
|
288
|
+
* **Appended lines go BEFORE the file's final newline, not after a blank one.** The
|
|
289
|
+
* append used to land after the trailing empty element of the split, which put a
|
|
290
|
+
* blank line above the new block — invisible when a key is written once and never
|
|
291
|
+
* removed, and one blank line per phase once a key comes and goes. The
|
|
292
|
+
* pre-existing text is still an untouched prefix either way.
|
|
234
293
|
*/
|
|
235
|
-
export declare function reconcileEnvText(existing: string | null, values: Record<string, string>, keys?: readonly string[]): string;
|
|
294
|
+
export declare function reconcileEnvText(existing: string | null, values: Record<string, string | null | undefined>, keys?: readonly string[]): string;
|
|
236
295
|
/**
|
|
237
296
|
* Read this engagement's machine-level credential, or `null` when this machine
|
|
238
297
|
* holds none for it.
|
|
@@ -254,8 +313,22 @@ export type EnvWriteOutcome = 'written' | 'skipped';
|
|
|
254
313
|
* The mode is re-applied on the skipped path too — a store that already existed at
|
|
255
314
|
* the wrong mode is exactly the case `writeFileSync`'s `mode` option cannot fix,
|
|
256
315
|
* and a re-install is the natural moment to repair it.
|
|
316
|
+
*
|
|
317
|
+
* **`keys` NARROWS WHAT THIS CALL OWNS, and it is why the phase stamp does not need
|
|
318
|
+
* a writer of its own.** Left off, this writes the eight credential keys, which is
|
|
319
|
+
* the installer's call and is unchanged. Passed a subset, it touches exactly those
|
|
320
|
+
* keys and copies every other line — ours and anyone else's — through untouched, so
|
|
321
|
+
* a caller that owns ONE key writes that key without re-implementing the
|
|
322
|
+
* read/merge/write/chmod sequence around it. That re-implementation is the failure
|
|
323
|
+
* this parameter exists to remove: a second writer that forgot `0o600` would loosen
|
|
324
|
+
* the credential file and pass every test that only checked which keys survived.
|
|
325
|
+
* A value of `null` deletes its key's line (see `reconcileEnvText`).
|
|
326
|
+
*
|
|
327
|
+
* The directory is created only when there is something to write. A removal-only
|
|
328
|
+
* call against a machine holding no store for this engagement leaves the disk
|
|
329
|
+
* alone rather than creating an empty file under a fresh directory.
|
|
257
330
|
*/
|
|
258
|
-
export declare function writeEngagementEnv(engagementId: string, values: Record<string, string>, home?: string): EnvWriteOutcome;
|
|
331
|
+
export declare function writeEngagementEnv(engagementId: string, values: Record<string, string | null | undefined>, home?: string, keys?: readonly string[]): EnvWriteOutcome;
|
|
259
332
|
/**
|
|
260
333
|
* The shell function both generated consumers embed to find this file.
|
|
261
334
|
*
|
|
@@ -291,6 +364,16 @@ export declare function writeEngagementEnv(engagementId: string, values: Record<
|
|
|
291
364
|
* `$HALFCYCLE_ENV_PROBLEM` names which step failed — `no-pin`, `no-id`, `no-home`,
|
|
292
365
|
* `no-credential` — so each caller says the true thing for its own surface instead
|
|
293
366
|
* of printing one sentence for four different situations.
|
|
367
|
+
*
|
|
368
|
+
* **HALFCYCLE_ENV_NOT_THIS_ACCOUNT IS SET ON THE `no-credential` FAILURE PATH TOO
|
|
369
|
+
* (T-02b, `solo-project-ownership`) — that is the state it exists to distinguish.**
|
|
370
|
+
* The marker check sits right after `$HOME` is confirmed and right before the
|
|
371
|
+
* credential file is tested, because `$hc_id` is known by then regardless of
|
|
372
|
+
* whether a credential exists — a not-this-account refusal is exactly a
|
|
373
|
+
* `no-credential` outcome with a reason attached, never a fourth
|
|
374
|
+
* `HALFCYCLE_ENV_PROBLEM` value: the credential is equally absent either way, and
|
|
375
|
+
* every existing reader of that variable stays correct without being taught a new
|
|
376
|
+
* case.
|
|
294
377
|
*/
|
|
295
378
|
export declare function engagementResolutionShell(): string;
|
|
296
379
|
//# sourceMappingURL=engagement-credential.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engagement-credential.d.ts","sourceRoot":"","sources":["../src/engagement-credential.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwFG;AAiBH;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,iBAAiB,CAAC;AAEtD,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7E;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,cAAc,8EAIjB,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,4BAA4B,mDAExC,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,mBAAmB,mMAOtB,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"engagement-credential.d.ts","sourceRoot":"","sources":["../src/engagement-credential.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwFG;AAiBH;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,iBAAiB,CAAC;AAEtD,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7E;AAyBD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAEpF;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAMnF;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,cAAc,8EAIjB,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,4BAA4B,mDAExC,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,mBAAmB,mMAOtB,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAmErD;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUhE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EACjD,IAAI,GAAE,SAAS,MAAM,EAAwB,GAC5C,MAAM,CAwDR;AAMD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,MAAM,GACZ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAM/B;AAED,8DAA8D;AAC9D,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,SAAS,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EACjD,IAAI,CAAC,EAAE,MAAM,EACb,IAAI,GAAE,SAAS,MAAM,EAAwB,GAC5C,eAAe,CAoBjB;AA4BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CA8ClD"}
|