pi-fold 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -7
- package/extensions/active-context.ts +2252 -182
- package/extensions/lib/advisory.ts +171 -14
- package/extensions/lib/canonical.ts +20 -0
- package/extensions/lib/curation.ts +437 -0
- package/extensions/lib/folding.ts +468 -22
- package/extensions/lib/instrumentation.ts +333 -0
- package/extensions/lib/measurement.ts +111 -0
- package/extensions/lib/persistence.ts +158 -2
- package/extensions/lib/policy.ts +380 -4
- package/extensions/lib/scheduling.ts +923 -0
- package/extensions/lib/selection.ts +437 -12
- package/extensions/lib/surfacing.ts +349 -0
- package/extensions/lib/tool-surface.ts +52 -3
- package/extensions/lib/transcript.ts +58 -4
- package/package.json +2 -2
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
EXPAND_LEASE_GENERATIONS,
|
|
23
23
|
MAX_ADVISORY_DELIVERIES_PER_MILESTONE,
|
|
24
24
|
MAX_EXPAND_LEASES,
|
|
25
|
+
MAX_PENDING_MARKS,
|
|
26
|
+
SURFACING_MAX_LOG_RECORDS,
|
|
25
27
|
} from "./policy.ts";
|
|
26
28
|
import type {
|
|
27
29
|
ActiveContextCheckpointV2,
|
|
@@ -35,7 +37,10 @@ import type {
|
|
|
35
37
|
FoldPart,
|
|
36
38
|
FoldRecordEntry,
|
|
37
39
|
FoldRecordRef,
|
|
40
|
+
MarkOrigin,
|
|
41
|
+
PendingMark,
|
|
38
42
|
PreparedFold,
|
|
43
|
+
SurfacingRecord,
|
|
39
44
|
} from "./policy.ts";
|
|
40
45
|
|
|
41
46
|
export const ACTIVE_FOLD_KEYS = [
|
|
@@ -49,6 +54,13 @@ export const PREPARED_FOLD_KEYS = [
|
|
|
49
54
|
export const ACTIVE_STATE_KEYS = ["version", "sessionId", "revision", "folds", "expanded", "protected"] as const;
|
|
50
55
|
export const FOLD_RECORD_REF_KEYS = ["id", "sha256"] as const;
|
|
51
56
|
export const FOLD_RECORD_ENTRY_KEYS = ["version", "sessionId", "foldId", "recordSha256", "fold"] as const;
|
|
57
|
+
export const SURFACING_RECORD_KEYS = ["source", "id", "score", "ordinal", "outcome"] as const;
|
|
58
|
+
export const SURFACING_OUTCOMES = Object.freeze(["shown", "accept", "reject"] as const);
|
|
59
|
+
export const PENDING_FOLD_MARK_KEYS = [
|
|
60
|
+
"mark", "id", "kind", "parts", "brief", "briefProvenance", "origin", "ordinal",
|
|
61
|
+
] as const;
|
|
62
|
+
export const PENDING_REFOLD_MARK_KEYS = ["mark", "id", "origin", "ordinal"] as const;
|
|
63
|
+
export const MARK_ORIGINS = Object.freeze(["agent", "ladder"] as const);
|
|
52
64
|
export const STATE_CHECKPOINT_V2_KEYS = [
|
|
53
65
|
"version", "kind", "sessionId", "revision", "foldRefs", "expanded", "protected", "prepared", "stateSha256",
|
|
54
66
|
] as const;
|
|
@@ -67,6 +79,85 @@ export function validTokensSinceToolFold(value: unknown): value is number {
|
|
|
67
79
|
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
68
80
|
}
|
|
69
81
|
|
|
82
|
+
export function validSurfacingRecord(value: unknown): value is SurfacingRecord {
|
|
83
|
+
if (!exactRecord(value, SURFACING_RECORD_KEYS)) return false;
|
|
84
|
+
const score = ownValue(value, "score");
|
|
85
|
+
return typeof ownValue(value, "source") === "string" && Boolean(ownValue(value, "source")) &&
|
|
86
|
+
typeof ownValue(value, "id") === "string" && Boolean(ownValue(value, "id")) &&
|
|
87
|
+
typeof score === "number" && Number.isFinite(score) && Number(score) >= 0 && Number(score) <= 1 &&
|
|
88
|
+
Number.isSafeInteger(ownValue(value, "ordinal")) && Number(ownValue(value, "ordinal")) >= 0 &&
|
|
89
|
+
SURFACING_OUTCOMES.includes(ownValue(value, "outcome") as SurfacingRecord["outcome"]);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function parseSurfacingLog(value: unknown): SurfacingRecord[] {
|
|
93
|
+
const records = denseOwnArrayValues(value);
|
|
94
|
+
if (!records || records.length > SURFACING_MAX_LOG_RECORDS || !records.every(validSurfacingRecord)) {
|
|
95
|
+
throw new Error("Invalid active-context surfacing log");
|
|
96
|
+
}
|
|
97
|
+
return clone(records) as SurfacingRecord[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Stable identity of one pending mark: its phase plus the fold it names. */
|
|
101
|
+
export function pendingMarkKey(mark: Pick<PendingMark, "mark" | "id">): string {
|
|
102
|
+
return `${mark.mark}:${mark.id}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function validPendingMark(value: unknown): value is PendingMark {
|
|
106
|
+
const id = ownValue(value, "id");
|
|
107
|
+
const ordinal = ownValue(value, "ordinal");
|
|
108
|
+
if (typeof id !== "string" || !id ||
|
|
109
|
+
!MARK_ORIGINS.includes(ownValue(value, "origin") as MarkOrigin) ||
|
|
110
|
+
!Number.isSafeInteger(ordinal) || Number(ordinal) < 0) return false;
|
|
111
|
+
const mark = ownValue(value, "mark");
|
|
112
|
+
if (mark === "refold") return exactRecord(value, PENDING_REFOLD_MARK_KEYS);
|
|
113
|
+
if (mark !== "fold" || !exactRecord(value, PENDING_FOLD_MARK_KEYS)) return false;
|
|
114
|
+
const kind = ownValue(value, "kind");
|
|
115
|
+
const parts = denseOwnArrayValues(ownValue(value, "parts"));
|
|
116
|
+
return (kind === "tool-result" || kind === "chapter" || kind === "consolidation") &&
|
|
117
|
+
Boolean(parts?.length) && parts!.length <= ACTIVE_CONTEXT_POLICY.maxFoldSourceRefs &&
|
|
118
|
+
parts!.every(validFoldPart) && structurallyValidBrief(ownValue(value, "brief")) &&
|
|
119
|
+
validProvenance(ownValue(value, "briefProvenance")) &&
|
|
120
|
+
id === foldIdFor(kind as FoldKind, parts as FoldPart[]);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Agent-corrected fold briefs, keyed by fold id. Bounded by the same brief cap a
|
|
125
|
+
* supplied brief obeys, and refused for a fold that is not in the forest, exactly
|
|
126
|
+
* like a pin or a refold mark naming a missing fold.
|
|
127
|
+
*/
|
|
128
|
+
export function parseBriefOverrides(
|
|
129
|
+
value: unknown,
|
|
130
|
+
ids?: ReadonlySet<string>,
|
|
131
|
+
): Record<string, string> {
|
|
132
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
133
|
+
throw new Error("Invalid active-context brief overrides");
|
|
134
|
+
}
|
|
135
|
+
const entries = Object.entries(value as Record<string, unknown>);
|
|
136
|
+
if (entries.length > MAX_ACTIVE_FOLD_RECORDS ||
|
|
137
|
+
entries.some(([id, brief]) => !id || typeof brief !== "string" || !brief.trim() ||
|
|
138
|
+
brief.length > ACTIVE_CONTEXT_POLICY.maxBriefChars || (ids && !ids.has(id)))) {
|
|
139
|
+
throw new Error("Invalid active-context brief overrides");
|
|
140
|
+
}
|
|
141
|
+
return Object.fromEntries(entries.sort(([left], [right]) => left.localeCompare(right))) as Record<string, string>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** The brief a fold PRESENTS: the agent's correction when it made one, else the record's. */
|
|
145
|
+
export function foldBrief(
|
|
146
|
+
fold: Pick<ActiveFold, "id" | "brief">,
|
|
147
|
+
state: Pick<ActiveContextState, "briefs">,
|
|
148
|
+
): string {
|
|
149
|
+
return state.briefs?.[fold.id] ?? fold.brief;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function parsePendingMarks(value: unknown): PendingMark[] {
|
|
153
|
+
const marks = denseOwnArrayValues(value);
|
|
154
|
+
if (!marks || marks.length > MAX_PENDING_MARKS || !marks.every(validPendingMark) ||
|
|
155
|
+
new Set((marks as PendingMark[]).map(pendingMarkKey)).size !== marks.length) {
|
|
156
|
+
throw new Error("Invalid active-context pending marks");
|
|
157
|
+
}
|
|
158
|
+
return clone(marks) as PendingMark[];
|
|
159
|
+
}
|
|
160
|
+
|
|
70
161
|
export function parseLeases(value: unknown, foldIds?: ReadonlySet<string>): Record<string, number> {
|
|
71
162
|
if (!isPlainRecord(value)) throw new Error("Invalid active-context expand leases");
|
|
72
163
|
const entries = Object.entries(value);
|
|
@@ -235,11 +326,23 @@ export function parseActiveContextState(
|
|
|
235
326
|
const hasTokensSinceToolFold = recordLike &&
|
|
236
327
|
Object.prototype.hasOwnProperty.call(value, "tokensSinceToolFold");
|
|
237
328
|
const hasLeases = recordLike && Object.prototype.hasOwnProperty.call(value, "leases");
|
|
329
|
+
const hasSurfacing = recordLike && Object.prototype.hasOwnProperty.call(value, "surfacing");
|
|
330
|
+
const hasPendingMarks = recordLike && Object.prototype.hasOwnProperty.call(value, "pendingMarks");
|
|
331
|
+
// Accepted, never read, never written. `pinnedPeeks` pinned a peek result against
|
|
332
|
+
// per-projection reclamation, which was deleted after rep 22 measured it rewriting
|
|
333
|
+
// the prefix mid-window. State written before that cut still carries the key, and
|
|
334
|
+
// rejecting it here would suspend automatic management on upgrade.
|
|
335
|
+
const hasPinnedPeeks = recordLike && Object.prototype.hasOwnProperty.call(value, "pinnedPeeks");
|
|
336
|
+
const hasBriefs = recordLike && Object.prototype.hasOwnProperty.call(value, "briefs");
|
|
238
337
|
const extraKeys = [
|
|
239
338
|
...(hasPrepared ? ["prepared"] : []),
|
|
240
339
|
...(hasAdvisory ? ["advisory"] : []),
|
|
241
340
|
...(hasTokensSinceToolFold ? ["tokensSinceToolFold"] : []),
|
|
242
341
|
...(hasLeases ? ["leases"] : []),
|
|
342
|
+
...(hasSurfacing ? ["surfacing"] : []),
|
|
343
|
+
...(hasPendingMarks ? ["pendingMarks"] : []),
|
|
344
|
+
...(hasPinnedPeeks ? ["pinnedPeeks"] : []),
|
|
345
|
+
...(hasBriefs ? ["briefs"] : []),
|
|
243
346
|
];
|
|
244
347
|
if (!exactRecord(value, [...ACTIVE_STATE_KEYS, ...extraKeys])) throw new Error("Invalid active-context state keys");
|
|
245
348
|
const folds = denseOwnArrayValues(ownValue(value, "folds"));
|
|
@@ -265,8 +368,14 @@ export function parseActiveContextState(
|
|
|
265
368
|
throw new Error("Invalid active-context tool-fold cadence");
|
|
266
369
|
}
|
|
267
370
|
const leases = hasLeases ? parseLeases(ownValue(value, "leases"), ids) : {};
|
|
371
|
+
const surfacing = hasSurfacing ? parseSurfacingLog(ownValue(value, "surfacing")) : [];
|
|
372
|
+
const marks = hasPendingMarks ? parsePendingMarks(ownValue(value, "pendingMarks")) : [];
|
|
373
|
+
if (marks.some((mark) => mark.mark === "refold" && !ids.has(mark.id))) {
|
|
374
|
+
throw new Error("Invalid active-context pending marks");
|
|
375
|
+
}
|
|
376
|
+
const briefs = hasBriefs ? parseBriefOverrides(ownValue(value, "briefs"), ids) : {};
|
|
268
377
|
const source = clone(value) as unknown as ActiveContextState;
|
|
269
|
-
// Provenance normalization is presentation-only
|
|
378
|
+
// Provenance normalization is presentation-only; never mutate a durable
|
|
270
379
|
// content-addressed fold record: changing its bytes causes the next re-persist
|
|
271
380
|
// to report a conflicting durable fold and suspend automatic management.
|
|
272
381
|
return {
|
|
@@ -280,6 +389,11 @@ export function parseActiveContextState(
|
|
|
280
389
|
? Number(ownValue(value, "tokensSinceToolFold"))
|
|
281
390
|
: 0,
|
|
282
391
|
leases,
|
|
392
|
+
// Omitted when empty so every state written before surfacing existed keeps its
|
|
393
|
+
// exact digest; the replay digest is order- and presence-sensitive by design.
|
|
394
|
+
...(surfacing.length ? { surfacing } : {}),
|
|
395
|
+
...(marks.length ? { pendingMarks: marks } : {}),
|
|
396
|
+
...(Object.keys(briefs).length ? { briefs } : {}),
|
|
283
397
|
...(hasAdvisory
|
|
284
398
|
? { advisory: clone(source.advisory!) }
|
|
285
399
|
: defaultAdvisory ? { advisory: { highWater: 0, delivered: {} } } : {}),
|
|
@@ -355,6 +469,9 @@ export function sameStateProjection(left: ActiveContextState, right: ActiveConte
|
|
|
355
469
|
const normalizedState = { ...clone(value), revision: 0 } as Partial<ActiveContextState>;
|
|
356
470
|
if (normalizedState.tokensSinceToolFold === 0) delete normalizedState.tokensSinceToolFold;
|
|
357
471
|
if (normalizedState.leases && Object.keys(normalizedState.leases).length === 0) delete normalizedState.leases;
|
|
472
|
+
if (normalizedState.surfacing && normalizedState.surfacing.length === 0) delete normalizedState.surfacing;
|
|
473
|
+
if (normalizedState.pendingMarks && normalizedState.pendingMarks.length === 0) delete normalizedState.pendingMarks;
|
|
474
|
+
if (normalizedState.briefs && !Object.keys(normalizedState.briefs).length) delete normalizedState.briefs;
|
|
358
475
|
return normalizedState;
|
|
359
476
|
};
|
|
360
477
|
return stableStringify(normalized(left)) === stableStringify(normalized(right));
|
|
@@ -395,6 +512,9 @@ export function validateV2ProjectionFields(
|
|
|
395
512
|
advisoryValue: unknown,
|
|
396
513
|
tokensSinceToolFoldValue: unknown,
|
|
397
514
|
leasesValue: unknown,
|
|
515
|
+
surfacingValue?: unknown,
|
|
516
|
+
pendingMarksValue?: unknown,
|
|
517
|
+
briefsValue?: unknown,
|
|
398
518
|
): {
|
|
399
519
|
expanded: string[];
|
|
400
520
|
protected: EvidenceRef[];
|
|
@@ -402,6 +522,9 @@ export function validateV2ProjectionFields(
|
|
|
402
522
|
advisory?: NonNullable<ActiveContextState["advisory"]>;
|
|
403
523
|
tokensSinceToolFold: number;
|
|
404
524
|
leases: Record<string, number>;
|
|
525
|
+
surfacing: SurfacingRecord[];
|
|
526
|
+
pendingMarks: PendingMark[];
|
|
527
|
+
briefs: Record<string, string>;
|
|
405
528
|
} {
|
|
406
529
|
const expanded = denseOwnArrayValues(expandedValue);
|
|
407
530
|
const protectedRefs = denseOwnArrayValues(protectedValue);
|
|
@@ -419,6 +542,9 @@ export function validateV2ProjectionFields(
|
|
|
419
542
|
throw new Error("Invalid active-context v2 tool-fold cadence");
|
|
420
543
|
}
|
|
421
544
|
const leases = leasesValue === undefined ? {} : parseLeases(leasesValue);
|
|
545
|
+
const surfacing = surfacingValue === undefined ? [] : parseSurfacingLog(surfacingValue);
|
|
546
|
+
const pendingMarks = pendingMarksValue === undefined ? [] : parsePendingMarks(pendingMarksValue);
|
|
547
|
+
const briefs = briefsValue === undefined ? {} : parseBriefOverrides(briefsValue);
|
|
422
548
|
return {
|
|
423
549
|
expanded: clone(expanded) as string[],
|
|
424
550
|
protected: clone(protectedRefs) as EvidenceRef[],
|
|
@@ -428,6 +554,9 @@ export function validateV2ProjectionFields(
|
|
|
428
554
|
}),
|
|
429
555
|
tokensSinceToolFold: tokensSinceToolFoldValue === undefined ? 0 : Number(tokensSinceToolFoldValue),
|
|
430
556
|
leases,
|
|
557
|
+
surfacing,
|
|
558
|
+
pendingMarks,
|
|
559
|
+
briefs,
|
|
431
560
|
};
|
|
432
561
|
}
|
|
433
562
|
|
|
@@ -439,10 +568,26 @@ export function parseActiveContextStateV2(value: unknown, sessionId: string): Ac
|
|
|
439
568
|
Object.prototype.hasOwnProperty.call(value, "tokensSinceToolFold"));
|
|
440
569
|
const hasLeases = Boolean(value && typeof value === "object" &&
|
|
441
570
|
Object.prototype.hasOwnProperty.call(value, "leases"));
|
|
571
|
+
const hasSurfacing = Boolean(value && typeof value === "object" &&
|
|
572
|
+
Object.prototype.hasOwnProperty.call(value, "surfacing"));
|
|
573
|
+
const hasPendingMarks = Boolean(value && typeof value === "object" &&
|
|
574
|
+
Object.prototype.hasOwnProperty.call(value, "pendingMarks"));
|
|
575
|
+
// Accepted, never read, never written. `pinnedPeeks` pinned a peek result against
|
|
576
|
+
// per-projection reclamation, which was deleted after rep 22 measured it rewriting
|
|
577
|
+
// the prefix mid-window. State written before that cut still carries the key, and
|
|
578
|
+
// rejecting it here would suspend automatic management on upgrade.
|
|
579
|
+
const hasPinnedPeeks = Boolean(value && typeof value === "object" &&
|
|
580
|
+
Object.prototype.hasOwnProperty.call(value, "pinnedPeeks"));
|
|
581
|
+
const hasBriefs = Boolean(value && typeof value === "object" &&
|
|
582
|
+
Object.prototype.hasOwnProperty.call(value, "briefs"));
|
|
442
583
|
const optionalKeys = [
|
|
443
584
|
...(hasAdvisory ? ["advisory"] : []),
|
|
444
585
|
...(hasTokensSinceToolFold ? ["tokensSinceToolFold"] : []),
|
|
445
586
|
...(hasLeases ? ["leases"] : []),
|
|
587
|
+
...(hasSurfacing ? ["surfacing"] : []),
|
|
588
|
+
...(hasPendingMarks ? ["pendingMarks"] : []),
|
|
589
|
+
...(hasPinnedPeeks ? ["pinnedPeeks"] : []),
|
|
590
|
+
...(hasBriefs ? ["briefs"] : []),
|
|
446
591
|
];
|
|
447
592
|
const checkpoint = kind === "checkpoint" &&
|
|
448
593
|
exactRecord(value, [...STATE_CHECKPOINT_V2_KEYS, ...optionalKeys]);
|
|
@@ -457,6 +602,7 @@ export function parseActiveContextStateV2(value: unknown, sessionId: string): Ac
|
|
|
457
602
|
validateV2ProjectionFields(
|
|
458
603
|
ownValue(value, "expanded"), ownValue(value, "protected"), ownValue(value, "prepared"),
|
|
459
604
|
ownValue(value, "advisory"), ownValue(value, "tokensSinceToolFold"), ownValue(value, "leases"),
|
|
605
|
+
ownValue(value, "surfacing"), ownValue(value, "pendingMarks"), ownValue(value, "briefs"),
|
|
460
606
|
);
|
|
461
607
|
if (checkpoint) {
|
|
462
608
|
const refs = denseOwnArrayValues(ownValue(value, "foldRefs"));
|
|
@@ -521,7 +667,7 @@ export function stateFromFoldRefs(
|
|
|
521
667
|
wire: Pick<
|
|
522
668
|
ActiveContextCheckpointV2,
|
|
523
669
|
"sessionId" | "revision" | "expanded" | "protected" | "prepared" | "advisory" |
|
|
524
|
-
"tokensSinceToolFold" | "leases"
|
|
670
|
+
"tokensSinceToolFold" | "leases" | "surfacing" | "pendingMarks" | "briefs"
|
|
525
671
|
>,
|
|
526
672
|
refs: FoldRecordRef[],
|
|
527
673
|
records: Map<string, FoldRecordEntry>,
|
|
@@ -541,6 +687,9 @@ export function stateFromFoldRefs(
|
|
|
541
687
|
protected: clone(wire.protected),
|
|
542
688
|
tokensSinceToolFold: wire.tokensSinceToolFold ?? 0,
|
|
543
689
|
leases: clone(wire.leases ?? {}),
|
|
690
|
+
...(wire.surfacing?.length ? { surfacing: clone(wire.surfacing) } : {}),
|
|
691
|
+
...(wire.pendingMarks?.length ? { pendingMarks: clone(wire.pendingMarks) } : {}),
|
|
692
|
+
...(wire.briefs && Object.keys(wire.briefs).length ? { briefs: clone(wire.briefs) } : {}),
|
|
544
693
|
...(wire.advisory === undefined ? {} : { advisory: clone(wire.advisory) }),
|
|
545
694
|
...(wire.prepared === null || wire.prepared === undefined ? {} : { prepared: clone(wire.prepared) }),
|
|
546
695
|
};
|
|
@@ -690,6 +839,9 @@ export function makeStateCheckpoint(state: ActiveContextState): ActiveContextChe
|
|
|
690
839
|
prepared: state.prepared ? clone(state.prepared) : null,
|
|
691
840
|
tokensSinceToolFold: state.tokensSinceToolFold,
|
|
692
841
|
leases: clone(state.leases),
|
|
842
|
+
...(state.surfacing?.length ? { surfacing: clone(state.surfacing) } : {}),
|
|
843
|
+
...(state.pendingMarks?.length ? { pendingMarks: clone(state.pendingMarks) } : {}),
|
|
844
|
+
...(state.briefs && Object.keys(state.briefs).length ? { briefs: clone(state.briefs) } : {}),
|
|
693
845
|
...(state.advisory ? { advisory: clone(state.advisory) } : {}),
|
|
694
846
|
stateSha256: semanticStateSha256(state),
|
|
695
847
|
}, state.sessionId) as ActiveContextCheckpointV2;
|
|
@@ -724,6 +876,9 @@ export function makeStateDelta(previous: ActiveContextState, next: ActiveContext
|
|
|
724
876
|
prepared: next.prepared ? clone(next.prepared) : null,
|
|
725
877
|
tokensSinceToolFold: next.tokensSinceToolFold,
|
|
726
878
|
leases: clone(next.leases),
|
|
879
|
+
...(next.surfacing?.length ? { surfacing: clone(next.surfacing) } : {}),
|
|
880
|
+
...(next.pendingMarks?.length ? { pendingMarks: clone(next.pendingMarks) } : {}),
|
|
881
|
+
...(next.briefs && Object.keys(next.briefs).length ? { briefs: clone(next.briefs) } : {}),
|
|
727
882
|
...(next.advisory ? { advisory: clone(next.advisory) } : {}),
|
|
728
883
|
stateSha256: semanticStateSha256(next),
|
|
729
884
|
}, next.sessionId) as ActiveContextDeltaV2;
|
|
@@ -738,6 +893,7 @@ export function protectionSha256(state: Pick<ActiveContextState, "protected">):
|
|
|
738
893
|
}
|
|
739
894
|
|
|
740
895
|
export const ADVISORY_BUDGETS: Readonly<Record<AdvisoryMilestone, number>> = Object.freeze({
|
|
896
|
+
orientation: 1,
|
|
741
897
|
notice: 2,
|
|
742
898
|
tools: 2,
|
|
743
899
|
chapters: 2,
|