@davesheffer/hunch 1.19.0 → 1.20.0-rc.2

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.
@@ -0,0 +1,271 @@
1
+ import { compareCodeUnits } from "./canonicalOrder.js";
2
+ import { EdgeSchema, ResourceSchema, isCredentialFreeText, } from "./types.js";
3
+ import { LANDSCAPE_CANDIDATE_SCHEMA_VERSION, LANDSCAPE_DISCOVERY_SCHEMA_VERSION, landscapeContentHash, } from "../extractors/landscapeDiscovery.js";
4
+ export const LANDSCAPE_REVIEW_SCHEMA_VERSION = "hunch.landscape-review/1";
5
+ export const LANDSCAPE_ADOPTION_RECEIPT_SCHEMA_VERSION = "hunch.landscape-adoption-receipt/1";
6
+ const SHA256 = /^sha256:[a-f0-9]{64}$/;
7
+ const MAX_SELECTIONS = 4_096;
8
+ function sortedUnique(values, label) {
9
+ if (values.length > MAX_SELECTIONS)
10
+ throw new Error(`landscape ${label} exceeds the bounded selection limit`);
11
+ const ordered = [...values].sort(compareCodeUnits);
12
+ for (let index = 1; index < ordered.length; index += 1) {
13
+ if (ordered[index] === ordered[index - 1])
14
+ throw new Error(`landscape ${label} contains a duplicate: ${ordered[index]}`);
15
+ }
16
+ return ordered;
17
+ }
18
+ function candidateUnsigned(candidate) {
19
+ return {
20
+ schema: candidate.schema,
21
+ authority: candidate.authority,
22
+ record: candidate.record,
23
+ evidence: candidate.evidence,
24
+ };
25
+ }
26
+ /** Refuse a result that was changed after exact-revision discovery. */
27
+ export function assertLandscapeDiscoveryIntegrity(discovery) {
28
+ if (discovery.schema !== LANDSCAPE_DISCOVERY_SCHEMA_VERSION || discovery.authority !== "candidate") {
29
+ throw new Error("landscape adoption requires a candidate discovery result");
30
+ }
31
+ if (!/^[a-f0-9]{40,64}$/.test(discovery.sourceRevision)) {
32
+ throw new Error("landscape discovery source revision is not an exact Git commit");
33
+ }
34
+ if (!discovery.repositoryRootIdentity || discovery.repositoryRootIdentity.length > 2_048
35
+ || !isCredentialFreeText(discovery.repositoryRootIdentity)) {
36
+ throw new Error("landscape discovery repository identity is invalid");
37
+ }
38
+ for (const candidate of discovery.resources)
39
+ ResourceSchema.parse(candidate.record);
40
+ for (const candidate of discovery.relationships)
41
+ EdgeSchema.parse(candidate.record);
42
+ const allCandidates = [...discovery.resources, ...discovery.relationships];
43
+ const seenHashes = new Set();
44
+ const seenIds = new Set();
45
+ for (const candidate of allCandidates) {
46
+ if (candidate.schema !== LANDSCAPE_CANDIDATE_SCHEMA_VERSION || candidate.authority !== "candidate") {
47
+ throw new Error("landscape discovery contains a non-candidate record");
48
+ }
49
+ if (candidate.record.metadata.discovery_authority !== "candidate") {
50
+ throw new Error(`landscape candidate ${candidate.record.id} does not retain candidate authority`);
51
+ }
52
+ if (candidate.record.currentness?.status !== "unverified"
53
+ || candidate.record.currentness.source_revision !== discovery.sourceRevision) {
54
+ throw new Error(`landscape candidate ${candidate.record.id} is not bound to the discovery revision`);
55
+ }
56
+ if (!candidate.evidence.length || candidate.evidence.length > 64
57
+ || candidate.evidence.some((evidence) => evidence.sourceRevision !== discovery.sourceRevision)) {
58
+ throw new Error(`landscape candidate ${candidate.record.id} has invalid revision evidence`);
59
+ }
60
+ const expected = landscapeContentHash(candidateUnsigned(candidate));
61
+ if (candidate.candidateHash !== expected || !SHA256.test(candidate.candidateHash)) {
62
+ throw new Error(`landscape candidate ${candidate.record.id} failed its content hash`);
63
+ }
64
+ if (seenHashes.has(candidate.candidateHash))
65
+ throw new Error(`landscape candidate hash is duplicated: ${candidate.candidateHash}`);
66
+ if (seenIds.has(candidate.record.id))
67
+ throw new Error(`landscape candidate id is duplicated: ${candidate.record.id}`);
68
+ seenHashes.add(candidate.candidateHash);
69
+ seenIds.add(candidate.record.id);
70
+ }
71
+ const unsigned = {
72
+ schema: discovery.schema,
73
+ authority: discovery.authority,
74
+ sourceRevision: discovery.sourceRevision,
75
+ repositoryRootIdentity: discovery.repositoryRootIdentity,
76
+ resources: discovery.resources,
77
+ relationships: discovery.relationships,
78
+ issues: discovery.issues,
79
+ };
80
+ if (discovery.discoveryHash !== landscapeContentHash(unsigned) || !SHA256.test(discovery.discoveryHash)) {
81
+ throw new Error("landscape discovery failed its content hash");
82
+ }
83
+ }
84
+ function reviewFor(input, selectedCandidateHashes) {
85
+ const reviewer = input.reviewer.trim();
86
+ if (!reviewer || reviewer.length > 128 || !isCredentialFreeText(reviewer)) {
87
+ throw new Error("landscape reviewer must be a credential-free label of 1-128 characters");
88
+ }
89
+ const reviewedAt = input.reviewedAt ?? new Date().toISOString();
90
+ if (reviewedAt.length > 64 || !Number.isFinite(Date.parse(reviewedAt))) {
91
+ throw new Error("landscape review timestamp must be ISO-compatible");
92
+ }
93
+ const acknowledgedIssueCodes = input.acknowledgeIssues
94
+ ? [...new Set(input.discovery.issues.map((issue) => issue.code))].sort(compareCodeUnits)
95
+ : [];
96
+ const unsigned = {
97
+ schema: LANDSCAPE_REVIEW_SCHEMA_VERSION,
98
+ authority: "human_confirmed",
99
+ reviewer,
100
+ reviewedAt,
101
+ discoveryHash: input.discovery.discoveryHash,
102
+ sourceRevision: input.discovery.sourceRevision,
103
+ repositoryRootIdentity: input.discovery.repositoryRootIdentity,
104
+ selectedCandidateHashes,
105
+ acknowledgedIssueCodes,
106
+ };
107
+ return { ...unsigned, reviewId: `lr_${landscapeContentHash(unsigned).slice("sha256:".length, "sha256:".length + 24)}` };
108
+ }
109
+ function acceptedMetadata(metadata, candidateHash, discoveryHash, review) {
110
+ return {
111
+ ...metadata,
112
+ discovery_authority: "human_confirmed",
113
+ landscape_candidate_hash: candidateHash,
114
+ landscape_discovery_hash: discoveryHash,
115
+ landscape_review_id: review.reviewId,
116
+ landscape_reviewed_by: review.reviewer,
117
+ landscape_reviewed_at: review.reviewedAt,
118
+ };
119
+ }
120
+ function acceptedResource(candidate, discoveryHash, review) {
121
+ return ResourceSchema.parse({
122
+ ...candidate.record,
123
+ provenance: {
124
+ ...candidate.record.provenance,
125
+ source: `${candidate.record.provenance.source}+human_confirmed`,
126
+ confidence: Math.max(candidate.record.provenance.confidence, 0.95),
127
+ last_verified: review.reviewedAt,
128
+ },
129
+ metadata: acceptedMetadata(candidate.record.metadata, candidate.candidateHash, discoveryHash, review),
130
+ currentness: {
131
+ ...candidate.record.currentness,
132
+ status: "current",
133
+ verified_at: review.reviewedAt,
134
+ },
135
+ updated_at: review.reviewedAt,
136
+ });
137
+ }
138
+ function acceptedRelationship(candidate, discoveryHash, review) {
139
+ return EdgeSchema.parse({
140
+ ...candidate.record,
141
+ provenance: {
142
+ ...candidate.record.provenance,
143
+ source: `${candidate.record.provenance.source}+human_confirmed`,
144
+ confidence: Math.max(candidate.record.provenance.confidence, 0.95),
145
+ last_verified: review.reviewedAt,
146
+ },
147
+ metadata: acceptedMetadata(candidate.record.metadata, candidate.candidateHash, discoveryHash, review),
148
+ currentness: {
149
+ ...candidate.record.currentness,
150
+ status: "current",
151
+ verified_at: review.reviewedAt,
152
+ },
153
+ });
154
+ }
155
+ function sameAcceptedCandidate(record, candidate, discoveryHash) {
156
+ const reviewId = record.metadata.landscape_review_id;
157
+ const reviewer = record.metadata.landscape_reviewed_by;
158
+ const reviewedAt = record.metadata.landscape_reviewed_at;
159
+ if (typeof reviewId !== "string" || !/^lr_[a-f0-9]{24}$/.test(reviewId)
160
+ || typeof reviewer !== "string" || !reviewer || reviewer.length > 128 || !isCredentialFreeText(reviewer)
161
+ || typeof reviewedAt !== "string" || !Number.isFinite(Date.parse(reviewedAt)))
162
+ return false;
163
+ const review = { reviewId, reviewer, reviewedAt };
164
+ const expected = record.schema === "hunch.resource/1"
165
+ ? acceptedResource(candidate, discoveryHash, review)
166
+ : acceptedRelationship(candidate, discoveryHash, review);
167
+ // Metadata hashes are evidence, not self-authenticating proof. Reuse only
168
+ // when the entire reviewed record still equals the candidate-derived value.
169
+ return landscapeContentHash(record) === landscapeContentHash(expected);
170
+ }
171
+ /**
172
+ * Convert an exact candidate discovery into a prevalidated write plan.
173
+ *
174
+ * Planning has no side effects. The caller persists only `resourcesToWrite` and
175
+ * `relationshipsToWrite` through the ordinary Hunch capture boundary after this
176
+ * function has proved every selection, endpoint and existing-record conflict.
177
+ */
178
+ export function planLandscapeAdoption(input) {
179
+ assertLandscapeDiscoveryIntegrity(input.discovery);
180
+ if (input.expectedDiscoveryHash !== input.discovery.discoveryHash) {
181
+ throw new Error("landscape discovery changed after review; review the current candidate set again");
182
+ }
183
+ if (input.discovery.issues.length > 0 && input.acknowledgeIssues !== true) {
184
+ throw new Error(`landscape discovery has ${input.discovery.issues.length} issue(s); inspect them and explicitly acknowledge them before adoption`);
185
+ }
186
+ const allCandidates = [
187
+ ...input.discovery.resources,
188
+ ...input.discovery.relationships,
189
+ ];
190
+ const allHashes = allCandidates.map((candidate) => candidate.candidateHash);
191
+ const selectedCandidateHashes = input.candidateHashes === "all"
192
+ ? [...allHashes].sort(compareCodeUnits)
193
+ : sortedUnique(input.candidateHashes, "candidate selection");
194
+ if (selectedCandidateHashes.length === 0)
195
+ throw new Error("landscape adoption requires at least one selected candidate");
196
+ const candidatesByHash = new Map(allCandidates.map((candidate) => [candidate.candidateHash, candidate]));
197
+ for (const hash of selectedCandidateHashes) {
198
+ if (!SHA256.test(hash) || !candidatesByHash.has(hash))
199
+ throw new Error(`landscape selection names an unknown candidate: ${hash}`);
200
+ }
201
+ const selected = new Set(selectedCandidateHashes);
202
+ const selectedResources = input.discovery.resources.filter((candidate) => selected.has(candidate.candidateHash));
203
+ const selectedRelationships = input.discovery.relationships.filter((candidate) => selected.has(candidate.candidateHash));
204
+ const selectedResourceIds = new Set(selectedResources.map((candidate) => candidate.record.id));
205
+ for (const candidate of selectedRelationships) {
206
+ if (!selectedResourceIds.has(candidate.record.from) || !selectedResourceIds.has(candidate.record.to)) {
207
+ throw new Error(`landscape relationship ${candidate.record.id} requires both endpoint resource candidates to be selected`);
208
+ }
209
+ }
210
+ const review = reviewFor(input, selectedCandidateHashes);
211
+ const existingResources = new Map();
212
+ for (const value of input.existingResources ?? []) {
213
+ const record = ResourceSchema.parse(value);
214
+ if (existingResources.has(record.id))
215
+ throw new Error(`landscape existing resources contain duplicate id: ${record.id}`);
216
+ existingResources.set(record.id, record);
217
+ }
218
+ const existingRelationships = new Map();
219
+ for (const value of input.existingRelationships ?? []) {
220
+ const record = EdgeSchema.parse(value);
221
+ if (existingRelationships.has(record.id))
222
+ throw new Error(`landscape existing relationships contain duplicate id: ${record.id}`);
223
+ existingRelationships.set(record.id, record);
224
+ }
225
+ const resourcesToWrite = [];
226
+ const relationshipsToWrite = [];
227
+ const reusedResourceIds = [];
228
+ const reusedRelationshipIds = [];
229
+ for (const candidate of selectedResources) {
230
+ const existing = existingResources.get(candidate.record.id);
231
+ if (existing) {
232
+ if (!sameAcceptedCandidate(existing, candidate, input.discovery.discoveryHash)) {
233
+ throw new Error(`landscape resource ${candidate.record.id} already exists with different reviewed content`);
234
+ }
235
+ reusedResourceIds.push(existing.id);
236
+ }
237
+ else {
238
+ resourcesToWrite.push(acceptedResource(candidate, input.discovery.discoveryHash, review));
239
+ }
240
+ }
241
+ for (const candidate of selectedRelationships) {
242
+ const existing = existingRelationships.get(candidate.record.id);
243
+ if (existing) {
244
+ if (!sameAcceptedCandidate(existing, candidate, input.discovery.discoveryHash)) {
245
+ throw new Error(`landscape relationship ${candidate.record.id} already exists with different reviewed content`);
246
+ }
247
+ reusedRelationshipIds.push(existing.id);
248
+ }
249
+ else {
250
+ relationshipsToWrite.push(acceptedRelationship(candidate, input.discovery.discoveryHash, review));
251
+ }
252
+ }
253
+ const receiptUnsigned = {
254
+ schema: LANDSCAPE_ADOPTION_RECEIPT_SCHEMA_VERSION,
255
+ authority: "human_confirmed",
256
+ review,
257
+ acceptedResourceIds: selectedResources.map((candidate) => candidate.record.id).sort(compareCodeUnits),
258
+ acceptedRelationshipIds: selectedRelationships.map((candidate) => candidate.record.id).sort(compareCodeUnits),
259
+ writtenResourceIds: resourcesToWrite.map((record) => record.id).sort(compareCodeUnits),
260
+ writtenRelationshipIds: relationshipsToWrite.map((record) => record.id).sort(compareCodeUnits),
261
+ reusedResourceIds: reusedResourceIds.sort(compareCodeUnits),
262
+ reusedRelationshipIds: reusedRelationshipIds.sort(compareCodeUnits),
263
+ };
264
+ const receiptId = `la_${landscapeContentHash(receiptUnsigned).slice("sha256:".length, "sha256:".length + 24)}`;
265
+ return {
266
+ receipt: { ...receiptUnsigned, receiptId },
267
+ resourcesToWrite,
268
+ relationshipsToWrite,
269
+ };
270
+ }
271
+ //# sourceMappingURL=landscapeAdoption.js.map
@@ -0,0 +1,281 @@
1
+ import { createHash } from "node:crypto";
2
+ import { compareCodeUnits } from "./canonicalOrder.js";
3
+ import { EdgeSchema, ResourceSchema, } from "./types.js";
4
+ export const LANDSCAPE_FRAGMENT_SCHEMA_VERSION = "hunch.landscape-fragment/1";
5
+ const SHA256 = /^sha256:[a-f0-9]{64}$/;
6
+ const REVIEW_ID = /^lr_[a-f0-9]{24}$/;
7
+ const DEFAULT_FRAGMENT_ITEMS = 8;
8
+ const MAX_FRAGMENT_ITEMS = 24;
9
+ const MAX_FRAGMENT_OMISSIONS = 16;
10
+ function canonical(value) {
11
+ if (Array.isArray(value))
12
+ return `[${value.map(canonical).join(",")}]`;
13
+ if (value && typeof value === "object") {
14
+ return `{${Object.entries(value)
15
+ .sort(([left], [right]) => compareCodeUnits(left, right))
16
+ .map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`)
17
+ .join(",")}}`;
18
+ }
19
+ return JSON.stringify(value) ?? "undefined";
20
+ }
21
+ export function landscapeFragmentHash(value) {
22
+ return `sha256:${createHash("sha256").update(canonical(value)).digest("hex")}`;
23
+ }
24
+ function reviewedCurrent(record) {
25
+ return record.metadata.discovery_authority === "human_confirmed"
26
+ && typeof record.metadata.landscape_candidate_hash === "string"
27
+ && SHA256.test(record.metadata.landscape_candidate_hash)
28
+ && typeof record.metadata.landscape_discovery_hash === "string"
29
+ && SHA256.test(record.metadata.landscape_discovery_hash)
30
+ && typeof record.metadata.landscape_review_id === "string"
31
+ && REVIEW_ID.test(record.metadata.landscape_review_id)
32
+ && record.currentness?.status === "current"
33
+ && typeof record.currentness.source_revision === "string"
34
+ && /^[a-f0-9]{40,64}$/.test(record.currentness.source_revision)
35
+ && record.provenance.source.split("+").includes("human_confirmed");
36
+ }
37
+ function tokens(value) {
38
+ return new Set((value.toLowerCase().match(/[a-z0-9][a-z0-9._/-]{2,}/g) ?? [])
39
+ .map((token) => token.replace(/^\/+|\/+$/g, ""))
40
+ .filter(Boolean));
41
+ }
42
+ function relevance(resource, target, targetTokens) {
43
+ const normalized = target.trim().toLowerCase();
44
+ const fields = [resource.id, resource.name, resource.locator ?? "", ...resource.scope];
45
+ if (fields.some((field) => field.toLowerCase() === normalized))
46
+ return { score: 1_000, reason: "exact-target" };
47
+ const recordTokens = tokens(fields.join(" "));
48
+ let overlap = 0;
49
+ for (const token of targetTokens)
50
+ if (recordTokens.has(token))
51
+ overlap++;
52
+ if (overlap > 0)
53
+ return { score: 700 + overlap * 20, reason: "task-match" };
54
+ if (resource.kind === "repository" && resource.scope.length === 0)
55
+ return { score: 500, reason: "orientation-root" };
56
+ return null;
57
+ }
58
+ /**
59
+ * Select a small orientation fragment from durable graph records.
60
+ *
61
+ * Candidate/unreviewed/stale records are excluded before ranking, so neither a
62
+ * good lexical match nor a graph edge can accidentally upgrade their authority.
63
+ */
64
+ export function selectReviewedLandscape(resources, relationships, target, maxItems = DEFAULT_FRAGMENT_ITEMS) {
65
+ const boundedItems = Number.isFinite(maxItems)
66
+ ? Math.max(1, Math.min(MAX_FRAGMENT_ITEMS, Math.floor(maxItems)))
67
+ : DEFAULT_FRAGMENT_ITEMS;
68
+ const currentResources = resources
69
+ .map((record) => ResourceSchema.parse(record))
70
+ .filter(reviewedCurrent)
71
+ .sort((left, right) => compareCodeUnits(left.id, right.id));
72
+ const currentIds = new Set(currentResources.map((record) => record.id));
73
+ const currentRelationships = relationships
74
+ .filter((record) => record.schema === "hunch.resource-relationship/1")
75
+ .map((record) => EdgeSchema.parse(record))
76
+ .filter((record) => reviewedCurrent(record) && currentIds.has(record.from) && currentIds.has(record.to))
77
+ .sort((left, right) => compareCodeUnits(left.id, right.id));
78
+ const targetTokens = tokens(target);
79
+ const scores = new Map();
80
+ for (const resource of currentResources) {
81
+ const match = relevance(resource, target, targetTokens);
82
+ if (match)
83
+ scores.set(resource.id, match);
84
+ }
85
+ // A matched/root node brings only its immediate reviewed neighbors into the
86
+ // candidate pool. This is orientation, not an unbounded organization crawl.
87
+ const initialIds = new Set(scores.keys());
88
+ for (const relationship of currentRelationships) {
89
+ const fromSelected = initialIds.has(relationship.from);
90
+ const toSelected = initialIds.has(relationship.to);
91
+ if (fromSelected === toSelected)
92
+ continue;
93
+ const neighborId = fromSelected ? relationship.to : relationship.from;
94
+ const existing = scores.get(neighborId);
95
+ if (!existing || existing.score < 350)
96
+ scores.set(neighborId, { score: 350, reason: "graph-neighbor" });
97
+ }
98
+ const rankedResources = currentResources
99
+ .filter((record) => scores.has(record.id))
100
+ .sort((left, right) => {
101
+ const leftScore = scores.get(left.id).score;
102
+ const rightScore = scores.get(right.id).score;
103
+ return rightScore - leftScore || compareCodeUnits(left.id, right.id);
104
+ });
105
+ // Reserve room for connections whenever the selected graph has any. Five
106
+ // nodes + three relationships is the default eight-headline orientation cap.
107
+ const resourceCap = Math.min(rankedResources.length, Math.max(1, Math.ceil(boundedItems * 0.625)));
108
+ const initiallyChosen = rankedResources.slice(0, resourceCap);
109
+ const orientationRoot = rankedResources.find((record) => record.kind === "repository" && record.scope.length === 0);
110
+ const chosenResources = orientationRoot && !initiallyChosen.some((record) => record.id === orientationRoot.id)
111
+ ? [...initiallyChosen.slice(0, Math.max(0, resourceCap - 1)), orientationRoot]
112
+ .sort((left, right) => rankedResources.indexOf(left) - rankedResources.indexOf(right))
113
+ : initiallyChosen;
114
+ const chosenIds = new Set(chosenResources.map((record) => record.id));
115
+ const connectable = currentRelationships.filter((record) => chosenIds.has(record.from) && chosenIds.has(record.to));
116
+ const relationshipCap = Math.max(0, boundedItems - chosenResources.length);
117
+ const chosenRelationships = connectable.slice(0, relationshipCap);
118
+ const omitted = [];
119
+ for (const record of rankedResources.filter((candidate) => !chosenIds.has(candidate.id))) {
120
+ if (omitted.length >= MAX_FRAGMENT_OMISSIONS)
121
+ break;
122
+ omitted.push({
123
+ kind: "resources",
124
+ recordId: record.id,
125
+ reason: "landscape-cap",
126
+ detail: `reviewed resource fell below the bounded ${boundedItems}-item landscape orientation cap`,
127
+ });
128
+ }
129
+ for (const record of connectable.slice(relationshipCap)) {
130
+ if (omitted.length >= MAX_FRAGMENT_OMISSIONS)
131
+ break;
132
+ omitted.push({
133
+ kind: "relationships",
134
+ recordId: record.id,
135
+ reason: "landscape-cap",
136
+ detail: `reviewed relationship fell below the bounded ${boundedItems}-item landscape orientation cap`,
137
+ });
138
+ }
139
+ return {
140
+ schema: LANDSCAPE_FRAGMENT_SCHEMA_VERSION,
141
+ authority: "human_confirmed",
142
+ target,
143
+ resources: chosenResources.map((record, index) => ({
144
+ record,
145
+ selectionReason: scores.get(record.id).reason,
146
+ selectionRank: index + 1,
147
+ })),
148
+ relationships: chosenRelationships.map((record, index) => ({
149
+ record,
150
+ selectionReason: "graph-connection",
151
+ selectionRank: chosenResources.length + index + 1,
152
+ })),
153
+ omitted,
154
+ };
155
+ }
156
+ function stringsFromMetadata(records, key) {
157
+ return [...new Set(records
158
+ .map((record) => record.metadata[key])
159
+ .filter((value) => typeof value === "string"))]
160
+ .sort(compareCodeUnits);
161
+ }
162
+ export function createLandscapeDeliveryFragment(input) {
163
+ const deliveredRecords = [
164
+ ...input.resources.map((item) => item.record),
165
+ ...input.relationships.map((item) => item.record),
166
+ ];
167
+ const unsigned = {
168
+ schema: LANDSCAPE_FRAGMENT_SCHEMA_VERSION,
169
+ authority: "human_confirmed",
170
+ target: input.selection.target,
171
+ resources: input.resources,
172
+ relationships: input.relationships,
173
+ omitted: [...input.omitted].sort((left, right) => compareCodeUnits(left.recordId, right.recordId) || compareCodeUnits(left.reason, right.reason)),
174
+ reviewIds: stringsFromMetadata(deliveredRecords, "landscape_review_id"),
175
+ discoveryHashes: stringsFromMetadata(deliveredRecords, "landscape_discovery_hash"),
176
+ sourceRevisions: [...new Set(deliveredRecords
177
+ .map((record) => record.currentness?.source_revision)
178
+ .filter((value) => typeof value === "string"))]
179
+ .sort(compareCodeUnits),
180
+ };
181
+ const fragment = { ...unsigned, fragmentHash: landscapeFragmentHash(unsigned) };
182
+ assertLandscapeDeliveryFragment(fragment);
183
+ return fragment;
184
+ }
185
+ export function assertLandscapeDeliveryFragment(value) {
186
+ if (value.schema !== LANDSCAPE_FRAGMENT_SCHEMA_VERSION || value.authority !== "human_confirmed") {
187
+ throw new Error("landscape delivery fragment schema or authority is invalid");
188
+ }
189
+ if (!value.target || value.target.length > 100_000)
190
+ throw new Error("landscape delivery target is invalid");
191
+ if (value.resources.length + value.relationships.length > MAX_FRAGMENT_ITEMS) {
192
+ throw new Error("landscape delivery fragment exceeds its item cap");
193
+ }
194
+ const resourceIds = new Set();
195
+ const ranks = new Set();
196
+ const selectionRanks = new Set();
197
+ for (const item of value.resources) {
198
+ ResourceSchema.parse(item.record);
199
+ if (!reviewedCurrent(item.record)) {
200
+ throw new Error("landscape delivery contains a non-reviewed resource");
201
+ }
202
+ if (resourceIds.has(item.record.id))
203
+ throw new Error("landscape delivery resource is duplicated");
204
+ resourceIds.add(item.record.id);
205
+ if (!Number.isSafeInteger(item.rank) || item.rank < 1 || ranks.has(item.rank))
206
+ throw new Error("landscape delivery rank is invalid");
207
+ ranks.add(item.rank);
208
+ if (!Number.isSafeInteger(item.selectionRank) || item.selectionRank < 1 || selectionRanks.has(item.selectionRank)
209
+ || !["exact-target", "task-match", "orientation-root", "graph-neighbor"].includes(item.selectionReason)) {
210
+ throw new Error("landscape delivery resource selection receipt is invalid");
211
+ }
212
+ selectionRanks.add(item.selectionRank);
213
+ if (item.deliveryReason !== "ranked" || item.required !== false || item.blocking !== false
214
+ || item.provenanceStatus !== "current" || !Number.isSafeInteger(item.tokenCost) || item.tokenCost < 1) {
215
+ throw new Error("landscape delivery resource receipt is invalid");
216
+ }
217
+ }
218
+ const relationshipIds = new Set();
219
+ for (const item of value.relationships) {
220
+ EdgeSchema.parse(item.record);
221
+ if (!reviewedCurrent(item.record) || !resourceIds.has(item.record.from) || !resourceIds.has(item.record.to)) {
222
+ throw new Error("landscape delivery relationship lacks reviewed delivered endpoints");
223
+ }
224
+ if (relationshipIds.has(item.record.id))
225
+ throw new Error("landscape delivery relationship is duplicated");
226
+ relationshipIds.add(item.record.id);
227
+ if (!Number.isSafeInteger(item.rank) || item.rank < 1 || ranks.has(item.rank))
228
+ throw new Error("landscape delivery rank is invalid");
229
+ ranks.add(item.rank);
230
+ if (!Number.isSafeInteger(item.selectionRank) || item.selectionRank < 1 || selectionRanks.has(item.selectionRank)
231
+ || item.selectionReason !== "graph-connection") {
232
+ throw new Error("landscape delivery relationship selection receipt is invalid");
233
+ }
234
+ selectionRanks.add(item.selectionRank);
235
+ if (item.deliveryReason !== "ranked" || item.required !== false || item.blocking !== false
236
+ || item.provenanceStatus !== "current" || !Number.isSafeInteger(item.tokenCost) || item.tokenCost < 1) {
237
+ throw new Error("landscape delivery relationship receipt is invalid");
238
+ }
239
+ }
240
+ if (value.omitted.length > MAX_FRAGMENT_OMISSIONS + MAX_FRAGMENT_ITEMS) {
241
+ throw new Error("landscape delivery omission evidence is unbounded");
242
+ }
243
+ const omissionKeys = new Set();
244
+ for (const item of value.omitted) {
245
+ const key = `${item.kind}:${item.recordId}:${item.reason}`;
246
+ if (!item.recordId || !item.detail || omissionKeys.has(key)
247
+ || !["budget", "stale-provenance", "endpoint-not-delivered", "landscape-cap"].includes(item.reason)) {
248
+ throw new Error("landscape delivery omission receipt is invalid");
249
+ }
250
+ omissionKeys.add(key);
251
+ }
252
+ for (const reviewId of value.reviewIds)
253
+ if (!REVIEW_ID.test(reviewId))
254
+ throw new Error("landscape delivery review id is invalid");
255
+ for (const discoveryHash of value.discoveryHashes)
256
+ if (!SHA256.test(discoveryHash))
257
+ throw new Error("landscape delivery discovery hash is invalid");
258
+ for (const revision of value.sourceRevisions)
259
+ if (!/^[a-f0-9]{40,64}$/.test(revision))
260
+ throw new Error("landscape delivery revision is invalid");
261
+ const deliveredRecords = [
262
+ ...value.resources.map((item) => item.record),
263
+ ...value.relationships.map((item) => item.record),
264
+ ];
265
+ const expectedReviewIds = stringsFromMetadata(deliveredRecords, "landscape_review_id");
266
+ const expectedDiscoveryHashes = stringsFromMetadata(deliveredRecords, "landscape_discovery_hash");
267
+ const expectedSourceRevisions = [...new Set(deliveredRecords
268
+ .map((record) => record.currentness?.source_revision)
269
+ .filter((revision) => typeof revision === "string"))]
270
+ .sort(compareCodeUnits);
271
+ if (canonical(value.reviewIds) !== canonical(expectedReviewIds)
272
+ || canonical(value.discoveryHashes) !== canonical(expectedDiscoveryHashes)
273
+ || canonical(value.sourceRevisions) !== canonical(expectedSourceRevisions)) {
274
+ throw new Error("landscape delivery evidence summary does not match its delivered records");
275
+ }
276
+ const { fragmentHash, ...unsigned } = value;
277
+ if (!SHA256.test(fragmentHash) || fragmentHash !== landscapeFragmentHash(unsigned)) {
278
+ throw new Error("landscape delivery fragment failed its content hash");
279
+ }
280
+ }
281
+ //# sourceMappingURL=landscapeDelivery.js.map