@cassiomc1/forgeloop 0.1.13 → 0.1.15

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/src/core/phase.js CHANGED
@@ -101,7 +101,7 @@ function reconcileImplementationStep(state, toPhase) {
101
101
  };
102
102
  }
103
103
 
104
- async function assertPhasePrerequisites(target, state, toPhase, packageRoot) {
104
+ async function assertPhasePrerequisites(target, state, toPhase, packageRoot, authorityContext, runtimeContext) {
105
105
  if (toPhase === "CONTRACT_READY" || toPhase === "ROUTED" || toPhase === "EXECUTING") {
106
106
  try {
107
107
  await readContract(target, packageRoot);
@@ -127,7 +127,7 @@ async function assertPhasePrerequisites(target, state, toPhase, packageRoot) {
127
127
  throw phaseError("E_PHASE_EVIDENCE_MISSING", "COMPLETE requires verification evidence");
128
128
  }
129
129
  if (toPhase === "COMPLETE") {
130
- const completion = await evaluateCompletion({ target, packageRoot, persist: false });
130
+ const completion = await evaluateCompletion({ target, packageRoot, persist: false, authorityContext, runtimeContext });
131
131
  if (completion.status !== "VALID") {
132
132
  throw phaseError("E_COMPLETION_REJECTED", "COMPLETE requires a valid completion audit", completion.errors.flatMap((error) => error.artifacts ?? []));
133
133
  }
@@ -136,11 +136,16 @@ async function assertPhasePrerequisites(target, state, toPhase, packageRoot) {
136
136
 
137
137
  export async function advanceWorkState(target, toPhase, options = {}) {
138
138
  const normalizedOptions = typeof options === "string" ? { packageRoot: options } : options;
139
- const { packageRoot, now = new Date().toISOString() } = normalizedOptions;
139
+ const {
140
+ packageRoot,
141
+ now = new Date().toISOString(),
142
+ authorityContext,
143
+ runtimeContext,
144
+ } = normalizedOptions;
140
145
  assertWorkPhase(toPhase);
141
146
  const state = await readWorkState(target, packageRoot);
142
147
  if (!state) throw phaseError("E_PHASE_PREREQUISITE_MISSING", "Cannot advance without work state", [ARTIFACT_PATHS.state]);
143
- await assertPhasePrerequisites(target, state, toPhase, packageRoot);
148
+ await assertPhasePrerequisites(target, state, toPhase, packageRoot, authorityContext, runtimeContext);
144
149
  await assertPersistedStateIdentity(target, state, toPhase, packageRoot);
145
150
  if (!isValidTransition(state.phase, toPhase)) {
146
151
  throw phaseError("E_PHASE_TRANSITION_INVALID", `Invalid work-state transition: ${state.phase} -> ${toPhase}`);
@@ -231,7 +236,12 @@ export async function advanceWorkState(target, toPhase, options = {}) {
231
236
  packageRoot,
232
237
  additionalEvidence: preflight.policy?.requiredEvidence ?? [],
233
238
  });
234
- await validateReceipt(receipt.value, packageRoot);
239
+ await validateReceipt(receipt.value, packageRoot, {
240
+ target,
241
+ taskId: contract?.value?.taskId,
242
+ authorityContext,
243
+ runtimeContext,
244
+ });
235
245
  assertCompletionRelationships({
236
246
  contract,
237
247
  route,
@@ -240,12 +250,21 @@ export async function advanceWorkState(target, toPhase, options = {}) {
240
250
  requiredEvidence,
241
251
  requireRequiredChecks: false,
242
252
  requireReceiptStateFingerprint: false,
253
+ target,
254
+ taskId: contract?.value?.taskId,
255
+ authorityContext,
256
+ runtimeContext,
243
257
  });
244
258
  nextReceipt = await createReceipt({
245
259
  ...receipt.value,
246
260
  stateFingerprint: canonicalFingerprint(next),
247
261
  verificationCycle: next.verificationCycle ?? receipt.value.verificationCycle ?? 1,
248
- }, packageRoot);
262
+ }, packageRoot, {
263
+ target,
264
+ taskId: contract?.value?.taskId,
265
+ authorityContext,
266
+ runtimeContext,
267
+ });
249
268
  assertCompletionRelationships({
250
269
  contract,
251
270
  route,
@@ -253,6 +272,10 @@ export async function advanceWorkState(target, toPhase, options = {}) {
253
272
  receipt: nextReceipt,
254
273
  requiredEvidence,
255
274
  requireRequiredChecks: false,
275
+ target,
276
+ taskId: contract?.value?.taskId,
277
+ authorityContext,
278
+ runtimeContext,
256
279
  });
257
280
  } catch (error) {
258
281
  if (error.code !== "ARTIFACT_MISSING") throw error;
@@ -81,6 +81,11 @@ export const FAILURE_CODES = Object.freeze([
81
81
  "E_TERMINAL_REQUIREMENT_NOT_TERMINAL",
82
82
  "E_TERMINAL_REQUIREMENT_TYPE_MISMATCH",
83
83
  "E_TERMINAL_STATUS_REGRESSION",
84
+ "E_INSTALLATION_AUTHORITY_REQUIRED",
85
+ "E_AUTHORITY_INVALID",
86
+ "E_AUTHORITY_SCOPE_MISMATCH",
87
+ "E_AUTHORITY_UNTRUSTED_SOURCE",
88
+ "E_VERIFICATION_TOOL_UNAVAILABLE",
84
89
  ]);
85
90
 
86
91
  export const WORK_PHASES = Object.freeze([
@@ -56,7 +56,7 @@ function assertKnownGuides(guides) {
56
56
  if (new Set(guides).size !== guides.length) throw new Error("Receipt selectedGuides must not contain duplicates");
57
57
  }
58
58
 
59
- export function assertReceiptSemantics(receipt) {
59
+ export function assertReceiptSemantics(receipt, options = {}) {
60
60
  const evidence = receipt.evidence ?? [];
61
61
  if (receipt.verificationCycle !== undefined
62
62
  && (!Number.isInteger(receipt.verificationCycle) || receipt.verificationCycle < 1)) {
@@ -66,7 +66,7 @@ export function assertReceiptSemantics(receipt) {
66
66
 
67
67
  for (const [index, check] of receipt.checks.entries()) {
68
68
  if (check?.schemaVersion !== undefined || check?.id !== undefined || check?.evidenceKind !== undefined) {
69
- assertCheck(check, `receipt.checks[${index}]`);
69
+ assertCheck(check, `receipt.checks[${index}]`, options);
70
70
  continue;
71
71
  }
72
72
  if (check?.status === "passed") {
@@ -132,15 +132,15 @@ export function assertReceiptSemantics(receipt) {
132
132
  return receipt;
133
133
  }
134
134
 
135
- export async function validateReceipt(receipt, packageRoot) {
135
+ export async function validateReceipt(receipt, packageRoot, options = {}) {
136
136
  assertSecretFree(receipt);
137
137
  const schema = await readSchema("execution-receipt", packageRoot);
138
138
  assertSchema(receipt, schema, "execution receipt");
139
139
  assertKnownGuides(receipt.selectedGuides);
140
- return assertReceiptSemantics(receipt);
140
+ return assertReceiptSemantics(receipt, options);
141
141
  }
142
142
 
143
- export async function createReceipt(input, packageRoot) {
143
+ export async function createReceipt(input, packageRoot, options = {}) {
144
144
  assertSecretFree(input);
145
145
  const receipt = {
146
146
  schemaVersion: RECEIPT_SCHEMA_VERSION,
@@ -169,5 +169,5 @@ export async function createReceipt(input, packageRoot) {
169
169
  deployed: false,
170
170
  },
171
171
  };
172
- return validateReceipt(receipt, packageRoot);
172
+ return validateReceipt(receipt, packageRoot, options);
173
173
  }
@@ -0,0 +1,80 @@
1
+ export const AUTHORITY_TRUST_MODES = Object.freeze(["NONE", "HOST_ATTESTED"]);
2
+
3
+ const AUTHORITY_CONTEXT_FIELDS = Object.freeze([
4
+ "trustedAuthorityFile",
5
+ "trustedAuthorityDir",
6
+ "authorities",
7
+ "authority",
8
+ ]);
9
+
10
+ function configuredValue(value) {
11
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : undefined;
12
+ }
13
+
14
+ function providerValue(provider) {
15
+ if (typeof provider === "function") return provider();
16
+ return provider && typeof provider === "object" && !Array.isArray(provider) ? provider : {};
17
+ }
18
+
19
+ function contextInput(input = {}) {
20
+ const source = input && typeof input === "object" && !Array.isArray(input) ? input : {};
21
+ const provider = providerValue(source.trustedAuthorityProvider);
22
+ const explicit = source.authorityContext && typeof source.authorityContext === "object"
23
+ ? source.authorityContext
24
+ : {};
25
+ const { authorityContext: _authorityContext, trustedAuthorityProvider: _trustedAuthorityProvider, ...direct } = source;
26
+ return { ...direct, ...provider, ...explicit };
27
+ }
28
+
29
+ export function hasAuthorityContext(options = {}) {
30
+ if (!options || typeof options !== "object") return false;
31
+ return Boolean(
32
+ options.authorityContext
33
+ || options.runtimeContext?.authorityContext
34
+ || (options.runtimeContext && typeof options.runtimeContext === "object" && options.runtimeContext.trustMode),
35
+ );
36
+ }
37
+
38
+ export function createAuthorityContext(input = {}) {
39
+ const values = contextInput(input);
40
+ const trustMode = values.trustMode ?? values.authorityTrustMode ?? "NONE";
41
+ if (!AUTHORITY_TRUST_MODES.includes(trustMode)) {
42
+ const error = new Error(`Unsupported authority trust mode: ${trustMode}`);
43
+ error.code = "E_AUTHORITY_INVALID";
44
+ throw error;
45
+ }
46
+
47
+ const context = { trustMode };
48
+ for (const field of AUTHORITY_CONTEXT_FIELDS) {
49
+ if (field === "trustedAuthorityFile" || field === "trustedAuthorityDir") {
50
+ const value = configuredValue(values[field]);
51
+ if (value !== undefined) context[field] = value;
52
+ } else if (values[field] !== undefined) {
53
+ context[field] = values[field];
54
+ }
55
+ }
56
+ return Object.freeze(context);
57
+ }
58
+
59
+ export function resolveAuthorityContext(options = {}) {
60
+ const source = options && typeof options === "object" && !Array.isArray(options) ? options : {};
61
+ if (source.authorityContext !== undefined) {
62
+ return createAuthorityContext(source.authorityContext);
63
+ }
64
+ if (source.runtimeContext?.authorityContext !== undefined) {
65
+ return createAuthorityContext(source.runtimeContext.authorityContext);
66
+ }
67
+ if (source.runtimeContext && typeof source.runtimeContext === "object" && source.runtimeContext.trustMode) {
68
+ return createAuthorityContext(source.runtimeContext);
69
+ }
70
+
71
+ const hasDirectAuthority = AUTHORITY_CONTEXT_FIELDS.some((field) => source[field] !== undefined);
72
+ // Direct resolver options are legacy source selectors, not a host attestation
73
+ // channel. They remain NONE unless wrapped in an explicit runtime context.
74
+ return hasDirectAuthority ? createAuthorityContext({ ...source, trustMode: "NONE" }) : createAuthorityContext();
75
+ }
76
+
77
+ export function createForgeLoopContext(options = {}) {
78
+ const authorityContext = createAuthorityContext(options);
79
+ return Object.freeze({ authorityContext });
80
+ }
@@ -24,6 +24,7 @@ export const SHIPPED_SCHEMA_NAMES = Object.freeze([
24
24
  "activation",
25
25
  "policy",
26
26
  "task-bundle",
27
+ "execution",
27
28
  ]);
28
29
 
29
30
  export class SchemaValidationError extends Error {
@@ -59,6 +59,7 @@ export const TEMPLATE_PATHS = [
59
59
  "schemas/config.schema.json",
60
60
  "schemas/preflight.schema.json",
61
61
  "schemas/check.schema.json",
62
+ "schemas/execution.schema.json",
62
63
  "schemas/evidence-coverage.schema.json",
63
64
  "schemas/event.schema.json",
64
65
  "schemas/activation.schema.json",
@@ -0,0 +1,296 @@
1
+ import { accessSync, constants as fsConstants, lstatSync, readFileSync, realpathSync } from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import { assertJsonBytes, assertJsonLimits } from "./json-safety.js";
5
+ import {
6
+ hasAuthorityContext,
7
+ resolveAuthorityContext,
8
+ } from "./runtime-context.js";
9
+
10
+ export const E_AUTHORITY_UNTRUSTED_SOURCE = "E_AUTHORITY_UNTRUSTED_SOURCE";
11
+ const E_AUTHORITY_INVALID = "E_AUTHORITY_INVALID";
12
+ const AUTHORITY_FILE_ENV = "FORGELOOP_AUTHORITY_FILE";
13
+ const AUTHORITY_DIR_ENV = "FORGELOOP_AUTHORITY_DIR";
14
+
15
+ function configuredValue(value) {
16
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
17
+ }
18
+
19
+ function authoritySourceOptions(options = {}) {
20
+ const context = resolveAuthorityContext(options);
21
+ const contextProvided = hasAuthorityContext(options);
22
+ const directFile = configuredValue(options.trustedAuthorityFile);
23
+ const directDir = configuredValue(options.trustedAuthorityDir);
24
+ const contextFile = configuredValue(context.trustedAuthorityFile);
25
+ const contextDir = configuredValue(context.trustedAuthorityDir);
26
+ const file = contextFile ?? (contextProvided ? null : directFile);
27
+ const dir = contextDir ?? (contextProvided ? null : directDir);
28
+ const authorities = context.authorities ?? (contextProvided ? undefined : options.authorities);
29
+ const authority = context.authority ?? (contextProvided ? undefined : options.authority);
30
+
31
+ if (contextProvided
32
+ && context.trustMode !== "HOST_ATTESTED"
33
+ && !file
34
+ && !dir
35
+ && authorities === undefined
36
+ && authority === undefined) {
37
+ const envFile = configuredValue(process.env[AUTHORITY_FILE_ENV]);
38
+ const envDir = configuredValue(process.env[AUTHORITY_DIR_ENV]);
39
+ return {
40
+ context,
41
+ file: envFile,
42
+ dir: envDir,
43
+ authorities: undefined,
44
+ authority: undefined,
45
+ sourceConfigured: Boolean(envFile || envDir),
46
+ sourceType: envFile ? "external-file" : envDir ? "external-dir" : null,
47
+ };
48
+ }
49
+
50
+ if (context.trustMode === "HOST_ATTESTED" || contextProvided || file || dir || authorities !== undefined || authority !== undefined) {
51
+ return {
52
+ context,
53
+ file,
54
+ dir,
55
+ authorities,
56
+ authority,
57
+ sourceConfigured: Boolean(file || dir || authorities !== undefined || authority !== undefined),
58
+ sourceType: file ? "external-file" : dir ? "external-dir" : authorities !== undefined || authority !== undefined ? "in-memory" : null,
59
+ };
60
+ }
61
+
62
+ const envFile = configuredValue(process.env[AUTHORITY_FILE_ENV]);
63
+ const envDir = configuredValue(process.env[AUTHORITY_DIR_ENV]);
64
+ return {
65
+ context,
66
+ file: envFile,
67
+ dir: envDir,
68
+ authorities: undefined,
69
+ authority: undefined,
70
+ sourceConfigured: Boolean(envFile || envDir),
71
+ sourceType: envFile ? "external-file" : envDir ? "external-dir" : null,
72
+ };
73
+ }
74
+
75
+ export function trustedAuthorityConfiguration(options = {}) {
76
+ const { context, sourceConfigured, sourceType, file, dir } = authoritySourceOptions(options);
77
+ const sourceInsideTarget = options.target && (file || dir)
78
+ ? isInsideTarget(options.target, file ?? dir)
79
+ : false;
80
+ return {
81
+ sourceConfigured,
82
+ sourceType,
83
+ trustMode: context.trustMode,
84
+ trusted: context.trustMode === "HOST_ATTESTED" && sourceConfigured && !sourceInsideTarget,
85
+ };
86
+ }
87
+
88
+ function invalid(message, details = {}) {
89
+ return {
90
+ trusted: false,
91
+ error: { code: E_AUTHORITY_INVALID, message },
92
+ ...details,
93
+ };
94
+ }
95
+
96
+ function untrusted(message) {
97
+ return {
98
+ trusted: false,
99
+ error: { code: E_AUTHORITY_UNTRUSTED_SOURCE, message },
100
+ };
101
+ }
102
+
103
+ function authorityFileName(authorityRef) {
104
+ if (typeof authorityRef !== "string" || authorityRef.trim() === "") return null;
105
+ const ref = authorityRef.trim();
106
+ if (ref === "." || ref === ".." || ref.includes("/") || ref.includes("\\")) return null;
107
+ return ref.endsWith(".json") ? ref : `${ref}.json`;
108
+ }
109
+
110
+ function resolvedPath(candidate) {
111
+ const absolute = path.resolve(candidate);
112
+ const missingSegments = [];
113
+ let current = absolute;
114
+ while (true) {
115
+ try {
116
+ const existing = realpathSync(current);
117
+ return path.resolve(existing, ...missingSegments);
118
+ } catch (error) {
119
+ if (error.code !== "ENOENT") throw error;
120
+ const parent = path.dirname(current);
121
+ if (parent === current) return absolute;
122
+ missingSegments.unshift(path.basename(current));
123
+ current = parent;
124
+ }
125
+ }
126
+ }
127
+
128
+ function isWithin(parent, candidate) {
129
+ const relative = path.relative(parent, candidate);
130
+ return relative === ""
131
+ || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
132
+ }
133
+
134
+ function isInsideTarget(target, candidate) {
135
+ if (!target) return false;
136
+ // Check both lexical paths and their real paths. The lexical check rejects
137
+ // actor-created symlinks inside the target that point at an external file.
138
+ return isWithin(path.resolve(target), path.resolve(candidate))
139
+ || isWithin(resolvedPath(target), resolvedPath(candidate));
140
+ }
141
+
142
+ function readExternalJson(candidate, target, kind) {
143
+ if (isInsideTarget(target, candidate)) {
144
+ return untrusted(`Configured trusted authority ${kind} must be outside the actor-writable target`);
145
+ }
146
+
147
+ try {
148
+ accessSync(candidate, fsConstants.R_OK);
149
+ const info = lstatSync(candidate);
150
+ if (kind === "file" && !info.isFile()) {
151
+ return invalid("Configured trusted authority file is not a regular file");
152
+ }
153
+ if (kind === "directory" && !info.isDirectory()) {
154
+ return invalid("Configured trusted authority directory is not a directory");
155
+ }
156
+ const bytes = readFileSync(candidate);
157
+ assertJsonBytes(bytes, `trusted authority ${kind}`);
158
+ const value = JSON.parse(bytes.toString("utf8"));
159
+ assertJsonLimits(value, `trusted authority ${kind}`);
160
+ return { value };
161
+ } catch {
162
+ return invalid(`Configured trusted authority ${kind} could not be read or parsed`);
163
+ }
164
+ }
165
+
166
+ function grantsFromValue(value) {
167
+ if (Array.isArray(value)) return value;
168
+ if (value && typeof value === "object" && Array.isArray(value.authorities)) {
169
+ if (value.schemaVersion !== 1 || value.protocolVersion !== 1) return null;
170
+ return value.authorities;
171
+ }
172
+ return value && typeof value === "object" ? [value] : null;
173
+ }
174
+
175
+ function findAuthority(value, authorityRef) {
176
+ if (value && typeof value === "object" && !Array.isArray(value)) {
177
+ const keyedAuthority = value[authorityRef];
178
+ if (keyedAuthority && typeof keyedAuthority === "object") return keyedAuthority;
179
+ }
180
+ const envelope = value && typeof value === "object" && !Array.isArray(value)
181
+ && Array.isArray(value.authorities)
182
+ ? value
183
+ : null;
184
+ const grants = grantsFromValue(value);
185
+ if (!grants) return null;
186
+ const authority = grants.find((item) => item?.authorityId === authorityRef || item?.id === authorityRef) ?? null;
187
+ if (!authority || !envelope) return authority;
188
+ return {
189
+ ...authority,
190
+ schemaVersion: envelope.schemaVersion,
191
+ protocolVersion: envelope.protocolVersion,
192
+ };
193
+ }
194
+
195
+ function findInMemoryAuthority(authorities, authority, authorityRef) {
196
+ if (authority !== undefined) {
197
+ return authority?.authorityId === authorityRef || authority?.id === authorityRef ? authority : null;
198
+ }
199
+ if (authorities !== undefined) {
200
+ return findAuthority(authorities, authorityRef);
201
+ }
202
+ return undefined;
203
+ }
204
+
205
+ function resolveFromExternalFile(authorityRef, target, file) {
206
+ const read = readExternalJson(file, target, "file");
207
+ if (read.error) return read;
208
+ const authority = findAuthority(read.value, authorityRef);
209
+ return authority
210
+ ? { trusted: true, authority, sourceType: "external-file" }
211
+ : invalid(`Referenced installation authority '${authorityRef}' was not found in the trusted authority file`);
212
+ }
213
+
214
+ function resolveFromExternalDirectory(authorityRef, target, dir) {
215
+ const fileName = authorityFileName(authorityRef);
216
+ if (!fileName) return invalid("Installation authority reference must be a simple authority ID");
217
+ if (isInsideTarget(target, dir)) {
218
+ return untrusted("Configured trusted authority directory must be outside the actor-writable target");
219
+ }
220
+ try {
221
+ accessSync(dir, fsConstants.R_OK);
222
+ if (!lstatSync(dir).isDirectory()) {
223
+ return invalid("Configured trusted authority directory is not a directory");
224
+ }
225
+ } catch {
226
+ return invalid("Configured trusted authority directory could not be read");
227
+ }
228
+ const authorityPath = path.join(dir, fileName);
229
+ const read = readExternalJson(authorityPath, target, "file");
230
+ if (read.error) return read;
231
+ const authority = findAuthority(read.value, authorityRef);
232
+ return authority
233
+ ? { trusted: true, authority, sourceType: "external-dir" }
234
+ : invalid(`Referenced installation authority '${authorityRef}' does not match the trusted authority file`);
235
+ }
236
+
237
+ function projectLocalAuthorityExists(authorityRef, target) {
238
+ const fileName = authorityFileName(authorityRef);
239
+ if (!fileName || !target) return false;
240
+ const localPath = path.join(target, ".forgeloop", "authorities", fileName);
241
+ try {
242
+ accessSync(localPath, fsConstants.F_OK);
243
+ return true;
244
+ } catch (error) {
245
+ if (error.code === "ENOENT") return false;
246
+ return true;
247
+ }
248
+ }
249
+
250
+ export function resolveTrustedAuthority({
251
+ authorityRef,
252
+ target,
253
+ trustedAuthorityFile,
254
+ trustedAuthorityDir,
255
+ authorities,
256
+ authority,
257
+ authorityContext,
258
+ runtimeContext,
259
+ } = {}) {
260
+ const fileName = authorityFileName(authorityRef);
261
+ if (!fileName) return invalid("Installation authority reference must be a simple authority ID");
262
+
263
+ const sources = authoritySourceOptions({
264
+ trustedAuthorityFile,
265
+ trustedAuthorityDir,
266
+ authorities,
267
+ authority,
268
+ authorityContext,
269
+ runtimeContext,
270
+ });
271
+ if (sources.sourceConfigured && sources.context.trustMode !== "HOST_ATTESTED") {
272
+ return untrusted("Standalone configuration selects an authority source but does not attest host authority");
273
+ }
274
+ if ((sources.file || sources.dir) && !target) {
275
+ return untrusted("A target path is required to validate trusted authority provenance");
276
+ }
277
+ if (sources.file) return resolveFromExternalFile(authorityRef, target, sources.file);
278
+ if (sources.dir) return resolveFromExternalDirectory(authorityRef, target, sources.dir);
279
+
280
+ // In-memory authorities are a host-injected policy interface. They never
281
+ // originate from project-local files and are intentionally resolved only
282
+ // after the explicit external sources above.
283
+ const inMemory = findInMemoryAuthority(sources.authorities, sources.authority, authorityRef);
284
+ if (inMemory !== undefined) {
285
+ return inMemory
286
+ ? { trusted: true, authority: inMemory, sourceType: "in-memory" }
287
+ : invalid(`Referenced installation authority '${authorityRef}' could not be resolved`);
288
+ }
289
+
290
+ if (projectLocalAuthorityExists(authorityRef, target)) {
291
+ return untrusted("Project-local authority artifacts are references only and are not trusted authority sources");
292
+ }
293
+ return invalid(`Referenced installation authority '${authorityRef}' could not be resolved`, {
294
+ sourceConfigured: sources.sourceConfigured,
295
+ });
296
+ }