@remembrance-ai/opencode-plugin 0.1.64 → 0.1.71
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 +32 -2
- package/opencode.json +2 -2
- package/package.json +2 -1
- package/scripts/hook-core.mjs +680 -137
- package/servers/remembrance-mcp.mjs +2960 -70
- package/skills/remembrancer/SKILL.md +71 -2
- package/skills/remembrancer/references/identity-preferences-topology.md +4 -1
- package/skills/remembrancer/references/remembrance-setup.md +72 -0
|
@@ -7,6 +7,10 @@ var __export = (target, all) => {
|
|
|
7
7
|
|
|
8
8
|
// ../core/src/ids.ts
|
|
9
9
|
import { createHash, randomUUID } from "node:crypto";
|
|
10
|
+
function createPublicId(prefix) {
|
|
11
|
+
const token = randomUUID().replaceAll("-", "").slice(0, 18);
|
|
12
|
+
return `${prefix}_${token}`;
|
|
13
|
+
}
|
|
10
14
|
function canonicalJson(value) {
|
|
11
15
|
return JSON.stringify(sortForHash(value));
|
|
12
16
|
}
|
|
@@ -4539,11 +4543,26 @@ function parseClientReleaseManifest(value) {
|
|
|
4539
4543
|
if (surfaces.length !== value.surfaces.length || new Set(surfaces).size !== surfaces.length) {
|
|
4540
4544
|
return null;
|
|
4541
4545
|
}
|
|
4546
|
+
const surfaceReleases = parseSurfaceReleases(value.surface_releases);
|
|
4547
|
+
if (value.surface_releases !== void 0 && !surfaceReleases) return null;
|
|
4548
|
+
if (surfaceReleases) {
|
|
4549
|
+
const entries = Object.entries(surfaceReleases);
|
|
4550
|
+
if (entries.length === 0) return null;
|
|
4551
|
+
const newestVersion = entries.reduce(
|
|
4552
|
+
(latest, [, release]) => compareClientVersions(release.version, latest) > 0 ? release.version : latest,
|
|
4553
|
+
entries[0][1].version
|
|
4554
|
+
);
|
|
4555
|
+
const newestSurfaces = entries.filter(([, release]) => release.version === newestVersion).map(([surface]) => surface);
|
|
4556
|
+
if (newestVersion !== latestVersion || newestSurfaces.length !== surfaces.length || newestSurfaces.some((surface) => !surfaces.includes(surface))) {
|
|
4557
|
+
return null;
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4542
4560
|
return {
|
|
4543
4561
|
schema_version: CLIENT_RELEASE_SCHEMA_VERSION,
|
|
4544
4562
|
latest_version: latestVersion,
|
|
4545
4563
|
published_at: new Date(value.published_at).toISOString(),
|
|
4546
|
-
surfaces
|
|
4564
|
+
surfaces,
|
|
4565
|
+
...surfaceReleases ? { surface_releases: surfaceReleases } : {}
|
|
4547
4566
|
};
|
|
4548
4567
|
}
|
|
4549
4568
|
function resolveClientUpdateStatus(input) {
|
|
@@ -4562,29 +4581,30 @@ function resolveClientUpdateStatus(input) {
|
|
|
4562
4581
|
reason: "invalid_manifest"
|
|
4563
4582
|
};
|
|
4564
4583
|
}
|
|
4565
|
-
|
|
4584
|
+
const release = clientSurfaceRelease(manifest, input.surface);
|
|
4585
|
+
if (!release) {
|
|
4566
4586
|
return {
|
|
4567
4587
|
status: "unavailable",
|
|
4568
4588
|
current_version: input.currentVersion,
|
|
4569
4589
|
reason: "surface_not_published"
|
|
4570
4590
|
};
|
|
4571
4591
|
}
|
|
4572
|
-
if (compareClientVersions(
|
|
4592
|
+
if (compareClientVersions(release.version, input.currentVersion) <= 0) {
|
|
4573
4593
|
return {
|
|
4574
4594
|
status: "current",
|
|
4575
4595
|
current_version: input.currentVersion,
|
|
4576
|
-
latest_version:
|
|
4596
|
+
latest_version: release.version
|
|
4577
4597
|
};
|
|
4578
4598
|
}
|
|
4579
4599
|
const guidance = CLIENT_UPDATE_GUIDANCE[input.surface];
|
|
4580
4600
|
return {
|
|
4581
4601
|
status: "update_available",
|
|
4582
4602
|
current_version: input.currentVersion,
|
|
4583
|
-
latest_version:
|
|
4603
|
+
latest_version: release.version,
|
|
4584
4604
|
guidance,
|
|
4585
4605
|
notice: clientUpdateNotice({
|
|
4586
4606
|
currentVersion: input.currentVersion,
|
|
4587
|
-
latestVersion:
|
|
4607
|
+
latestVersion: release.version,
|
|
4588
4608
|
guidance
|
|
4589
4609
|
})
|
|
4590
4610
|
};
|
|
@@ -4604,6 +4624,38 @@ ${input.guidance.command}` : "";
|
|
|
4604
4624
|
function isRecord(value) {
|
|
4605
4625
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4606
4626
|
}
|
|
4627
|
+
function clientSurfaceRelease(manifest, surface) {
|
|
4628
|
+
const exact = manifest.surface_releases?.[surface];
|
|
4629
|
+
if (exact) return exact;
|
|
4630
|
+
return manifest.surfaces.includes(surface) ? {
|
|
4631
|
+
version: manifest.latest_version,
|
|
4632
|
+
published_at: manifest.published_at
|
|
4633
|
+
} : null;
|
|
4634
|
+
}
|
|
4635
|
+
function parseSurfaceReleases(value) {
|
|
4636
|
+
if (value === void 0) return void 0;
|
|
4637
|
+
if (!isRecord(value)) return null;
|
|
4638
|
+
const validSurfaces = new Set(CLIENT_RELEASE_SURFACES);
|
|
4639
|
+
const entries = Object.entries(value);
|
|
4640
|
+
if (entries.some(
|
|
4641
|
+
([surface, release]) => !validSurfaces.has(surface) || !isRecord(release) || Object.keys(release).some(
|
|
4642
|
+
(key) => key !== "version" && key !== "published_at"
|
|
4643
|
+
) || !parseStableClientVersion(release.version) || typeof release.published_at !== "string" || !isCanonicalIsoTimestamp(release.published_at)
|
|
4644
|
+
)) {
|
|
4645
|
+
return null;
|
|
4646
|
+
}
|
|
4647
|
+
return Object.fromEntries(
|
|
4648
|
+
entries.map(([surface, release]) => [
|
|
4649
|
+
surface,
|
|
4650
|
+
{
|
|
4651
|
+
version: release.version,
|
|
4652
|
+
published_at: new Date(
|
|
4653
|
+
release.published_at
|
|
4654
|
+
).toISOString()
|
|
4655
|
+
}
|
|
4656
|
+
])
|
|
4657
|
+
);
|
|
4658
|
+
}
|
|
4607
4659
|
function isCanonicalIsoTimestamp(value) {
|
|
4608
4660
|
const timestamp = Date.parse(value);
|
|
4609
4661
|
return Number.isFinite(timestamp) && new Date(timestamp).toISOString() === value;
|
|
@@ -5286,6 +5338,31 @@ var preferenceSettingSchema = preferenceSettingBaseSchema.superRefine(
|
|
|
5286
5338
|
}
|
|
5287
5339
|
}
|
|
5288
5340
|
);
|
|
5341
|
+
var preferenceValidationResultSchema = external_exports.object({
|
|
5342
|
+
decision: external_exports.enum(["approve", "reject", "uncertain"]),
|
|
5343
|
+
reason_code: external_exports.enum([
|
|
5344
|
+
"normalized",
|
|
5345
|
+
"unsafe_instruction",
|
|
5346
|
+
"malformed",
|
|
5347
|
+
"uncertain"
|
|
5348
|
+
]),
|
|
5349
|
+
normalized_setting: preferenceSettingSchema.nullable()
|
|
5350
|
+
}).strict().superRefine((result, ctx) => {
|
|
5351
|
+
if (result.decision === "approve" && !result.normalized_setting) {
|
|
5352
|
+
ctx.addIssue({
|
|
5353
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5354
|
+
path: ["normalized_setting"],
|
|
5355
|
+
message: "An approved preference requires a normalized setting"
|
|
5356
|
+
});
|
|
5357
|
+
}
|
|
5358
|
+
if (result.decision !== "approve" && result.normalized_setting) {
|
|
5359
|
+
ctx.addIssue({
|
|
5360
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5361
|
+
path: ["normalized_setting"],
|
|
5362
|
+
message: "Inactive preference validation cannot return a setting"
|
|
5363
|
+
});
|
|
5364
|
+
}
|
|
5365
|
+
});
|
|
5289
5366
|
var preferenceScopeSchema = external_exports.enum([
|
|
5290
5367
|
"task",
|
|
5291
5368
|
"organization",
|
|
@@ -5388,13 +5465,31 @@ var preferenceCompatibilityAssessmentSchema = external_exports.object({
|
|
|
5388
5465
|
rationale: external_exports.string().trim().min(1).max(200).nullable().default(null),
|
|
5389
5466
|
classification_version: external_exports.string().trim().min(1).max(96),
|
|
5390
5467
|
source: preferenceCompatibilitySourceSchema,
|
|
5391
|
-
locked: external_exports.boolean().default(false)
|
|
5468
|
+
locked: external_exports.boolean().default(false),
|
|
5469
|
+
// Whether this assessment carries enough authority to make a skill
|
|
5470
|
+
// ineligible, as opposed to merely reordering it. A model classification
|
|
5471
|
+
// is advisory on its own: an autonomous down-rank is bounded, reversible,
|
|
5472
|
+
// and invisible, but "unusable organization-wide" is a policy action that
|
|
5473
|
+
// needs a human in the loop. The persistence layer decides this and the
|
|
5474
|
+
// ranking layer consumes one boolean, so the authority rule lives in one
|
|
5475
|
+
// place. Defaults to advisory so absent or older data can never block.
|
|
5476
|
+
enforcement: external_exports.enum(["advisory", "blocking"]).default("advisory")
|
|
5392
5477
|
}).strict();
|
|
5393
5478
|
var preferenceCompatibilityAssessmentsSchema = external_exports.array(preferenceCompatibilityAssessmentSchema).max(MAX_EFFECTIVE_PREFERENCES * 2);
|
|
5394
5479
|
var preferenceCompatibilityClassifierResultSchema = external_exports.object({
|
|
5395
5480
|
relationship: preferenceCompatibilityRelationshipSchema,
|
|
5396
5481
|
confidence: external_exports.number().min(0).max(1),
|
|
5397
|
-
|
|
5482
|
+
reason_code: external_exports.enum([
|
|
5483
|
+
"behavior_supported",
|
|
5484
|
+
"behavior_conflicted",
|
|
5485
|
+
"required_step_conflict",
|
|
5486
|
+
"insufficient_evidence",
|
|
5487
|
+
"not_applicable",
|
|
5488
|
+
"unsafe_or_malformed_input"
|
|
5489
|
+
]),
|
|
5490
|
+
// Retained for private admin inspection only. Agent-facing explanations
|
|
5491
|
+
// are rendered from trusted templates rather than replaying model text.
|
|
5492
|
+
rationale: external_exports.string().trim().min(1).max(200).nullable().default(null),
|
|
5398
5493
|
// `locked` means the skill declares this behavior as a required step. It
|
|
5399
5494
|
// can block surgical application after an explicit invocation, but never
|
|
5400
5495
|
// lets a preference bypass a skill or organization requirement.
|
|
@@ -5566,6 +5661,685 @@ function buildOrganizationSkillHandoffBundle(input, createdAt = /* @__PURE__ */
|
|
|
5566
5661
|
});
|
|
5567
5662
|
}
|
|
5568
5663
|
|
|
5664
|
+
// ../core/src/private-lessons.ts
|
|
5665
|
+
import { createPublicKey, verify } from "node:crypto";
|
|
5666
|
+
var PRIVATE_LESSON_CONTRACT_VERSION = "private-lesson-contract-v1";
|
|
5667
|
+
var PRIVATE_LESSON_REDACTION_VERSION = "private-lesson-redaction-v2";
|
|
5668
|
+
var PRIVATE_LESSON_POLICY_VERSION = "private-lesson-policy-v1";
|
|
5669
|
+
var PRIVATE_LESSON_RECEIPT_VERSION = "private-lesson-receipt-v1";
|
|
5670
|
+
var PRIVATE_LESSON_HOLD_EVENT_VERSION = "private-lesson-hold-event-v1";
|
|
5671
|
+
var PRIVATE_LESSON_HOLD_RECEIPT_VERSION = "private-lesson-hold-receipt-v1";
|
|
5672
|
+
var PRIVATE_LESSON_SUPPORTED_REDACTION_VERSIONS = [
|
|
5673
|
+
PRIVATE_LESSON_REDACTION_VERSION
|
|
5674
|
+
];
|
|
5675
|
+
var PRIVATE_LESSON_MAX_PAYLOAD_BYTES = 4 * 1024;
|
|
5676
|
+
var PRIVATE_LESSON_MAX_REQUEST_BYTES = 8 * 1024;
|
|
5677
|
+
var sha256Schema = external_exports.string().regex(/^sha256:[a-f0-9]{64}$/);
|
|
5678
|
+
var opaqueCorrelationSchema = external_exports.string().regex(/^corr_[a-f0-9]{48}$/);
|
|
5679
|
+
var PRIVATE_LESSON_TAG_PATTERN = /^[a-z0-9][a-z0-9_-]{0,47}$/;
|
|
5680
|
+
var privateLessonTagSyntaxSchema = external_exports.string().regex(PRIVATE_LESSON_TAG_PATTERN);
|
|
5681
|
+
var PRIVATE_LESSON_SAFE_TAG_ALIASES = Object.freeze({
|
|
5682
|
+
catalog: "workflow",
|
|
5683
|
+
durability: "reliability",
|
|
5684
|
+
"host-approval": "authorization",
|
|
5685
|
+
mongo: "database",
|
|
5686
|
+
"private-lesson-test": "testing",
|
|
5687
|
+
"private-lessons": "workflow",
|
|
5688
|
+
"provider-failure": "reliability",
|
|
5689
|
+
retry: "retries",
|
|
5690
|
+
"tenant-isolation": "privacy",
|
|
5691
|
+
"visual-review": "ui"
|
|
5692
|
+
});
|
|
5693
|
+
var PRIVATE_LESSON_TAG_IDENTITY_HINTS = /* @__PURE__ */ new Set([
|
|
5694
|
+
"account",
|
|
5695
|
+
"client",
|
|
5696
|
+
"corp",
|
|
5697
|
+
"customer",
|
|
5698
|
+
"internal",
|
|
5699
|
+
"org",
|
|
5700
|
+
"organization",
|
|
5701
|
+
"private",
|
|
5702
|
+
"project",
|
|
5703
|
+
"repo",
|
|
5704
|
+
"repository",
|
|
5705
|
+
"tenant",
|
|
5706
|
+
"user"
|
|
5707
|
+
]);
|
|
5708
|
+
var privateLessonCanonicalTagSchema = privateLessonTagSyntaxSchema.superRefine((tag, ctx) => {
|
|
5709
|
+
const inspected = inspectPrivateLessonTag(tag);
|
|
5710
|
+
if (inspected.tag !== tag || inspected.held_reasons.length > 0) {
|
|
5711
|
+
ctx.addIssue({
|
|
5712
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5713
|
+
message: "tag must be canonical and pass private-lesson safety checks"
|
|
5714
|
+
});
|
|
5715
|
+
}
|
|
5716
|
+
});
|
|
5717
|
+
var privateLessonEventTypeSchema = external_exports.enum([
|
|
5718
|
+
"failure",
|
|
5719
|
+
"correction",
|
|
5720
|
+
"reusable_workflow"
|
|
5721
|
+
]);
|
|
5722
|
+
var privateLessonTierSchema = external_exports.enum([
|
|
5723
|
+
"structured_metadata",
|
|
5724
|
+
"bounded_redacted_prose"
|
|
5725
|
+
]);
|
|
5726
|
+
var privateLessonRedactionVersionSchema = external_exports.enum(
|
|
5727
|
+
PRIVATE_LESSON_SUPPORTED_REDACTION_VERSIONS
|
|
5728
|
+
);
|
|
5729
|
+
var privateLessonRedactionCategorySchema = external_exports.enum([
|
|
5730
|
+
"secret",
|
|
5731
|
+
"credential",
|
|
5732
|
+
"cookie",
|
|
5733
|
+
"jwt",
|
|
5734
|
+
"connection_string",
|
|
5735
|
+
"email",
|
|
5736
|
+
"ip_address",
|
|
5737
|
+
"hostname",
|
|
5738
|
+
"private_url",
|
|
5739
|
+
"public_url",
|
|
5740
|
+
"path",
|
|
5741
|
+
"identifier",
|
|
5742
|
+
"encoded_or_high_entropy",
|
|
5743
|
+
"screenshot",
|
|
5744
|
+
"stack_trace",
|
|
5745
|
+
"code_block",
|
|
5746
|
+
"unicode_control"
|
|
5747
|
+
]);
|
|
5748
|
+
var redactionCountsShape = Object.fromEntries(
|
|
5749
|
+
privateLessonRedactionCategorySchema.options.map((category) => [
|
|
5750
|
+
category,
|
|
5751
|
+
external_exports.number().int().min(0).max(999)
|
|
5752
|
+
])
|
|
5753
|
+
);
|
|
5754
|
+
var privateLessonRedactionCountsSchema = external_exports.object(redactionCountsShape).partial().strict().transform(
|
|
5755
|
+
(counts) => Object.fromEntries(
|
|
5756
|
+
Object.entries(counts).filter(([, count]) => Number(count) > 0).sort(([left], [right]) => left.localeCompare(right))
|
|
5757
|
+
)
|
|
5758
|
+
);
|
|
5759
|
+
var generalizedTextSchema = external_exports.string().trim().min(1).max(1200).superRefine(assertNetworkSafeLessonText);
|
|
5760
|
+
var conditionSchema = external_exports.string().trim().min(1).max(240).superRefine(assertNetworkSafeLessonText);
|
|
5761
|
+
var privateLessonCanonicalPayloadSchema = external_exports.object({
|
|
5762
|
+
contract_version: external_exports.literal(PRIVATE_LESSON_CONTRACT_VERSION),
|
|
5763
|
+
redaction_version: privateLessonRedactionVersionSchema,
|
|
5764
|
+
event_type: privateLessonEventTypeSchema,
|
|
5765
|
+
tier: privateLessonTierSchema,
|
|
5766
|
+
generalized_lesson: generalizedTextSchema.nullable(),
|
|
5767
|
+
stable_conditions: external_exports.array(conditionSchema).max(12),
|
|
5768
|
+
tags: external_exports.array(privateLessonCanonicalTagSchema).max(12),
|
|
5769
|
+
correlation_ids: external_exports.array(opaqueCorrelationSchema).max(8),
|
|
5770
|
+
redaction_counts: privateLessonRedactionCountsSchema,
|
|
5771
|
+
evidence_hashes: external_exports.array(sha256Schema).max(16)
|
|
5772
|
+
}).strict().superRefine((payload, ctx) => {
|
|
5773
|
+
if (payload.tier === "bounded_redacted_prose" && !payload.generalized_lesson) {
|
|
5774
|
+
ctx.addIssue({
|
|
5775
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5776
|
+
message: "bounded_redacted_prose requires generalized_lesson",
|
|
5777
|
+
path: ["generalized_lesson"]
|
|
5778
|
+
});
|
|
5779
|
+
}
|
|
5780
|
+
if (payload.tier === "structured_metadata" && payload.generalized_lesson !== null) {
|
|
5781
|
+
ctx.addIssue({
|
|
5782
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5783
|
+
message: "structured_metadata cannot include generalized_lesson",
|
|
5784
|
+
path: ["generalized_lesson"]
|
|
5785
|
+
});
|
|
5786
|
+
}
|
|
5787
|
+
});
|
|
5788
|
+
var privateLessonAttestationBaseSchema = external_exports.object({
|
|
5789
|
+
version: external_exports.literal("private-lesson-attestation-v1"),
|
|
5790
|
+
purpose: external_exports.literal("remembrance-private-lesson-submission"),
|
|
5791
|
+
provider: external_exports.enum(["codex", "claude_code", "cursor", "other"]),
|
|
5792
|
+
challenge_id: external_exports.string().regex(/^ach_[A-Za-z0-9_-]{8,96}$/),
|
|
5793
|
+
key_id: external_exports.string().min(1).max(160),
|
|
5794
|
+
algorithm: external_exports.literal("ed25519"),
|
|
5795
|
+
nonce: external_exports.string().min(16).max(160),
|
|
5796
|
+
audience: external_exports.literal("remembrance-private-lesson"),
|
|
5797
|
+
organization_audience_hash: sha256Schema,
|
|
5798
|
+
policy_digest: sha256Schema,
|
|
5799
|
+
redactor_digest: sha256Schema,
|
|
5800
|
+
issued_at: external_exports.string().datetime(),
|
|
5801
|
+
expires_at: external_exports.string().datetime(),
|
|
5802
|
+
signature: external_exports.string().min(32).max(512)
|
|
5803
|
+
}).strict();
|
|
5804
|
+
var privateLessonCandidateAttestationSchema = privateLessonAttestationBaseSchema.extend({
|
|
5805
|
+
record_type: external_exports.literal("candidate").optional(),
|
|
5806
|
+
payload_digest: sha256Schema
|
|
5807
|
+
});
|
|
5808
|
+
var privateLessonHoldAttestationSchema = privateLessonAttestationBaseSchema.extend({
|
|
5809
|
+
record_type: external_exports.literal("held_safety_event"),
|
|
5810
|
+
event_digest: sha256Schema
|
|
5811
|
+
});
|
|
5812
|
+
var privateLessonAttestationSchema = external_exports.union([
|
|
5813
|
+
privateLessonCandidateAttestationSchema,
|
|
5814
|
+
privateLessonHoldAttestationSchema
|
|
5815
|
+
]);
|
|
5816
|
+
var privateLessonSubmissionSchema = external_exports.object({
|
|
5817
|
+
candidate: privateLessonCanonicalPayloadSchema,
|
|
5818
|
+
payload_digest: sha256Schema,
|
|
5819
|
+
policy_digest: sha256Schema,
|
|
5820
|
+
redactor_digest: sha256Schema,
|
|
5821
|
+
idempotency_key: external_exports.string().min(16).max(160),
|
|
5822
|
+
attestation: privateLessonCandidateAttestationSchema
|
|
5823
|
+
}).strict();
|
|
5824
|
+
var privateLessonHoldEventPayloadSchema = external_exports.object({
|
|
5825
|
+
version: external_exports.literal(PRIVATE_LESSON_HOLD_EVENT_VERSION),
|
|
5826
|
+
record_type: external_exports.literal("held_safety_event"),
|
|
5827
|
+
event_type: privateLessonEventTypeSchema,
|
|
5828
|
+
held_categories: external_exports.array(privateLessonRedactionCategorySchema).min(1).max(privateLessonRedactionCategorySchema.options.length).superRefine((categories, ctx) => {
|
|
5829
|
+
if (new Set(categories).size !== categories.length) {
|
|
5830
|
+
ctx.addIssue({
|
|
5831
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5832
|
+
message: "held categories must be unique"
|
|
5833
|
+
});
|
|
5834
|
+
}
|
|
5835
|
+
}).transform((categories) => [...new Set(categories)].sort()),
|
|
5836
|
+
redaction_counts: privateLessonRedactionCountsSchema,
|
|
5837
|
+
contract_version: external_exports.literal(PRIVATE_LESSON_CONTRACT_VERSION),
|
|
5838
|
+
redaction_version: privateLessonRedactionVersionSchema,
|
|
5839
|
+
redactor_digest: sha256Schema,
|
|
5840
|
+
policy_digest: sha256Schema
|
|
5841
|
+
}).strict().superRefine((event, ctx) => {
|
|
5842
|
+
const held = new Set(event.held_categories);
|
|
5843
|
+
for (const category of event.held_categories) {
|
|
5844
|
+
if (!event.redaction_counts[category]) {
|
|
5845
|
+
ctx.addIssue({
|
|
5846
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5847
|
+
message: "held category requires a positive redaction count",
|
|
5848
|
+
path: ["redaction_counts", category]
|
|
5849
|
+
});
|
|
5850
|
+
}
|
|
5851
|
+
}
|
|
5852
|
+
for (const category of Object.keys(event.redaction_counts)) {
|
|
5853
|
+
if (!held.has(category)) {
|
|
5854
|
+
ctx.addIssue({
|
|
5855
|
+
code: external_exports.ZodIssueCode.custom,
|
|
5856
|
+
message: "redaction counts may include held categories only",
|
|
5857
|
+
path: ["redaction_counts", category]
|
|
5858
|
+
});
|
|
5859
|
+
}
|
|
5860
|
+
}
|
|
5861
|
+
});
|
|
5862
|
+
var privateLessonHoldSubmissionSchema = external_exports.object({
|
|
5863
|
+
record_type: external_exports.literal("held_safety_event"),
|
|
5864
|
+
event: privateLessonHoldEventPayloadSchema,
|
|
5865
|
+
event_digest: sha256Schema,
|
|
5866
|
+
idempotency_key: external_exports.string().min(16).max(160),
|
|
5867
|
+
attestation: privateLessonHoldAttestationSchema
|
|
5868
|
+
}).strict();
|
|
5869
|
+
var privateLessonEndpointSubmissionSchema = external_exports.union([
|
|
5870
|
+
privateLessonSubmissionSchema,
|
|
5871
|
+
privateLessonHoldSubmissionSchema
|
|
5872
|
+
]);
|
|
5873
|
+
var privateLessonPolicyPayloadSchema = external_exports.object({
|
|
5874
|
+
version: external_exports.literal(PRIVATE_LESSON_POLICY_VERSION),
|
|
5875
|
+
purpose: external_exports.literal("remembrance-private-lesson-policy"),
|
|
5876
|
+
organization_audience_hash: sha256Schema,
|
|
5877
|
+
policy_sequence: external_exports.number().int().positive(),
|
|
5878
|
+
enabled: external_exports.boolean(),
|
|
5879
|
+
enabled_tiers: external_exports.array(privateLessonTierSchema).max(2),
|
|
5880
|
+
action: external_exports.literal("submit_private_lesson_candidate"),
|
|
5881
|
+
endpoint: external_exports.literal("/api/v1/agent/private-lessons"),
|
|
5882
|
+
max_payload_bytes: external_exports.literal(PRIVATE_LESSON_MAX_PAYLOAD_BYTES),
|
|
5883
|
+
encryption_ready: external_exports.boolean(),
|
|
5884
|
+
kill_switch: external_exports.boolean(),
|
|
5885
|
+
public_fallback: external_exports.literal(false),
|
|
5886
|
+
contract_version: external_exports.literal(PRIVATE_LESSON_CONTRACT_VERSION),
|
|
5887
|
+
redaction_version: external_exports.literal(PRIVATE_LESSON_REDACTION_VERSION),
|
|
5888
|
+
redactor_digest: sha256Schema,
|
|
5889
|
+
issued_at: external_exports.string().datetime(),
|
|
5890
|
+
expires_at: external_exports.string().datetime()
|
|
5891
|
+
}).strict();
|
|
5892
|
+
var privateLessonReceiptPayloadSchema = external_exports.object({
|
|
5893
|
+
version: external_exports.literal(PRIVATE_LESSON_RECEIPT_VERSION),
|
|
5894
|
+
purpose: external_exports.literal("remembrance-private-lesson-receipt"),
|
|
5895
|
+
receipt_id: external_exports.string().regex(/^plr_[A-Za-z0-9_-]{8,96}$/),
|
|
5896
|
+
public_id: external_exports.string().regex(/^rpub_[A-Za-z0-9_-]{8,96}$/),
|
|
5897
|
+
organization_audience_hash: sha256Schema,
|
|
5898
|
+
status: external_exports.literal("accepted_private_candidate"),
|
|
5899
|
+
processing_state: external_exports.literal("queued_for_verification"),
|
|
5900
|
+
payload_digest: sha256Schema,
|
|
5901
|
+
policy_digest: sha256Schema,
|
|
5902
|
+
contract_version: external_exports.literal(PRIVATE_LESSON_CONTRACT_VERSION),
|
|
5903
|
+
redaction_version: privateLessonRedactionVersionSchema,
|
|
5904
|
+
redactor_digest: sha256Schema,
|
|
5905
|
+
issued_at: external_exports.string().datetime()
|
|
5906
|
+
}).strict();
|
|
5907
|
+
var privateLessonHoldReceiptPayloadSchema = external_exports.object({
|
|
5908
|
+
version: external_exports.literal(PRIVATE_LESSON_HOLD_RECEIPT_VERSION),
|
|
5909
|
+
purpose: external_exports.literal("remembrance-private-lesson-hold-receipt"),
|
|
5910
|
+
receipt_id: external_exports.string().regex(/^plhr_[A-Za-z0-9_-]{8,96}$/),
|
|
5911
|
+
organization_audience_hash: sha256Schema,
|
|
5912
|
+
status: external_exports.literal("accepted_hold_telemetry"),
|
|
5913
|
+
processing_state: external_exports.literal("recorded"),
|
|
5914
|
+
event_digest: sha256Schema,
|
|
5915
|
+
policy_digest: sha256Schema,
|
|
5916
|
+
contract_version: external_exports.literal(PRIVATE_LESSON_CONTRACT_VERSION),
|
|
5917
|
+
redaction_version: privateLessonRedactionVersionSchema,
|
|
5918
|
+
redactor_digest: sha256Schema,
|
|
5919
|
+
issued_at: external_exports.string().datetime()
|
|
5920
|
+
}).strict();
|
|
5921
|
+
var privateLessonSignedEnvelopeSchema = (payloadSchema) => external_exports.object({
|
|
5922
|
+
payload: payloadSchema,
|
|
5923
|
+
canonical_payload: external_exports.string().min(2).max(8192),
|
|
5924
|
+
signature: external_exports.string().min(32).max(512),
|
|
5925
|
+
key_id: external_exports.string().min(1).max(160),
|
|
5926
|
+
algorithm: external_exports.literal("Ed25519")
|
|
5927
|
+
}).strict();
|
|
5928
|
+
var signedPrivateLessonPolicySchema = privateLessonSignedEnvelopeSchema(privateLessonPolicyPayloadSchema);
|
|
5929
|
+
var signedPrivateLessonReceiptSchema = privateLessonSignedEnvelopeSchema(privateLessonReceiptPayloadSchema);
|
|
5930
|
+
var signedPrivateLessonHoldReceiptSchema = privateLessonSignedEnvelopeSchema(privateLessonHoldReceiptPayloadSchema);
|
|
5931
|
+
var privateLessonVerificationKeySchema = external_exports.object({
|
|
5932
|
+
kty: external_exports.literal("OKP"),
|
|
5933
|
+
crv: external_exports.literal("Ed25519"),
|
|
5934
|
+
x: external_exports.string().min(20),
|
|
5935
|
+
kid: external_exports.string().min(1).max(160),
|
|
5936
|
+
use: external_exports.literal("sig"),
|
|
5937
|
+
alg: external_exports.literal("EdDSA")
|
|
5938
|
+
}).strict();
|
|
5939
|
+
var privateLessonVerificationKeySetSchema = external_exports.object({ keys: external_exports.array(privateLessonVerificationKeySchema).min(1).max(16) }).strict();
|
|
5940
|
+
var privateLessonPolicyTrustFloorSchema = external_exports.object({
|
|
5941
|
+
organization_audience_hash: sha256Schema,
|
|
5942
|
+
policy_sequence: external_exports.number().int().positive(),
|
|
5943
|
+
policy_digest: sha256Schema
|
|
5944
|
+
}).strict();
|
|
5945
|
+
var privateLessonPreparationInputSchema = external_exports.object({
|
|
5946
|
+
event_type: privateLessonEventTypeSchema,
|
|
5947
|
+
generalized_lesson: external_exports.string().max(8192).nullable().optional(),
|
|
5948
|
+
stable_conditions: external_exports.array(external_exports.string().max(2048)).max(12).optional(),
|
|
5949
|
+
tags: external_exports.array(external_exports.string().max(96)).max(12).optional(),
|
|
5950
|
+
correlation_ids: external_exports.array(external_exports.string().max(160)).max(8).optional(),
|
|
5951
|
+
evidence_hashes: external_exports.array(external_exports.string().max(96)).max(16).optional()
|
|
5952
|
+
}).strict();
|
|
5953
|
+
var ZERO_WIDTH_OR_BIDI = /[\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFEFF]/gu;
|
|
5954
|
+
var CODE_BLOCK = /```[\s\S]*?```/gu;
|
|
5955
|
+
var STACK_TRACE = /(?:^|\n)\s*(?:at\s+\S+\s*\([^\n]+:\d+:\d+\)|File\s+"[^"]+",\s+line\s+\d+)/gmu;
|
|
5956
|
+
var SCREENSHOT = /!\[[^\]]*\]\([^)]*\)|\bdata:image\/(?:png|jpe?g|gif|webp);base64,[A-Za-z0-9+/=]+|\b[A-Za-z0-9_-]+\.(?:png|jpe?g|gif|webp)\b/giu;
|
|
5957
|
+
var CONNECTION_STRING = /\b(?:mongodb(?:\+srv)?|postgres(?:ql)?|mysql|redis|rediss):\/\/[^\s]+/giu;
|
|
5958
|
+
var PRIVATE_URL = /\bhttps?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]|10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|[^\s/]+\.(?:local|internal|corp))(?:\/[^\s]*)?/giu;
|
|
5959
|
+
var PUBLIC_URL = /\bhttps?:\/\/[^\s]+/giu;
|
|
5960
|
+
var EMAIL = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/giu;
|
|
5961
|
+
var IP_ADDRESS = /\b(?:\d{1,3}\.){3}\d{1,3}\b|\b(?:[A-F0-9]{1,4}:){1,7}:[A-F0-9]{0,4}\b|\b(?:[A-F0-9]{1,4}:){2,7}[A-F0-9]{1,4}\b/giu;
|
|
5962
|
+
var FILE_PATH = /(?<![A-Za-z0-9.*~:\/\\-])(?:(?:~\/|\.{1,2}\/|\/[A-Za-z0-9.*-]+\/|[A-Za-z]:\\)[^\s),;]*[A-Za-z0-9_*~=-]|[A-Za-z0-9.*_-]+\/[^\s),;]*\/[^\s),;]*[A-Za-z0-9_*~=-]|[A-Za-z0-9.*_-]+\/[A-Za-z0-9._-]*\.[A-Za-z0-9]{1,10}\b)/gmu;
|
|
5963
|
+
var JWT = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/gu;
|
|
5964
|
+
var COOKIE = /\b(?:cookie|set-cookie|session(?:_id)?|csrf(?:_token)?)\s*[:=]\s*[^\s;,]+/giu;
|
|
5965
|
+
var CREDENTIAL = /\b(?:api[_-]?key|access[_-]?token|auth(?:orization)?|password|passwd|private[_-]?key|client[_-]?secret)\s*[:=]\s*[^\s;,]+/giu;
|
|
5966
|
+
var SECRET = /\b(?:sk|pk|rk|ghp|github_pat|xox[baprs]|AKIA)[-_A-Za-z0-9]{12,}\b/gu;
|
|
5967
|
+
var INTERNAL_HOSTNAME = /\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:local|internal|corp|lan)\b/giu;
|
|
5968
|
+
var HOSTNAME = /\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?!(?:c|cc|cpp|cs|css|go|h|hpp|html|java|js|json|jsx|kt|lock|md|mjs|proto|py|rb|rs|scss|sh|sql|swift|toml|ts|tsx|xml|yaml|yml|zsh)\b)(?:[a-z]{2,63}|local|internal|corp|lan)\b/gu;
|
|
5969
|
+
var IDENTIFIER = /\b(?:org|user|member|account|tenant|repo|job|request|trace)[_-][A-Za-z0-9_-]{8,}\b|\b[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b|\b[0-9a-f]{24}\b|\barn:aws:[^\s;,]+/giu;
|
|
5970
|
+
var SHORT_PADDED_BASE64 = /(?<![A-Za-z0-9+/=])(?:[A-Za-z0-9+/]{4}){4,8}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)(?![A-Za-z0-9+/=])/gu;
|
|
5971
|
+
var SHORT_MIXED_OPAQUE = /(?<![A-Za-z0-9+/_=-])(?=[A-Za-z0-9+/_=-]{20,39}(?![A-Za-z0-9+/_=-]))(?=[A-Za-z0-9+/_=-]*[a-z])(?=[A-Za-z0-9+/_=-]*[A-Z])(?=[A-Za-z0-9+/_=-]*\d)(?=[A-Za-z0-9+/_=-]*[+/_=-])[A-Za-z0-9+/_=-]{20,39}(?![A-Za-z0-9+/_=-])/gu;
|
|
5972
|
+
var SHORT_CONTEXTUAL_CREDENTIAL = /\b(?:api[_ -]?key|access[_ -]?token|auth(?:orization)?|password|passwd|private[_ -]?key|client[_ -]?secret|token|credential|secret)\b\s+(?:is\s+)?(?=[^\s;,]{12,39}(?:[\s;,]|$))(?=[^\s;,]*[0-9+/_=-])[A-Za-z0-9+/_=-]{12,39}\b/giu;
|
|
5973
|
+
var HIGH_ENTROPY = /\b[A-Za-z0-9+/_=-]{40,}\b/gu;
|
|
5974
|
+
var EXISTING_REDACTION_MARKER = new RegExp(
|
|
5975
|
+
`\\[redacted:(?:${privateLessonRedactionCategorySchema.options.join("|")})\\]`,
|
|
5976
|
+
"gu"
|
|
5977
|
+
);
|
|
5978
|
+
var REPLACEMENTS = [
|
|
5979
|
+
{ category: "unicode_control", pattern: ZERO_WIDTH_OR_BIDI, hold: true },
|
|
5980
|
+
{ category: "stack_trace", pattern: STACK_TRACE, hold: true },
|
|
5981
|
+
{ category: "screenshot", pattern: SCREENSHOT, hold: true },
|
|
5982
|
+
{ category: "connection_string", pattern: CONNECTION_STRING, hold: false },
|
|
5983
|
+
{ category: "private_url", pattern: PRIVATE_URL, hold: false },
|
|
5984
|
+
{ category: "public_url", pattern: PUBLIC_URL, hold: true },
|
|
5985
|
+
{ category: "jwt", pattern: JWT, hold: false },
|
|
5986
|
+
{ category: "cookie", pattern: COOKIE, hold: false },
|
|
5987
|
+
{ category: "credential", pattern: CREDENTIAL, hold: false },
|
|
5988
|
+
{ category: "secret", pattern: SECRET, hold: false },
|
|
5989
|
+
{ category: "email", pattern: EMAIL, hold: false },
|
|
5990
|
+
{ category: "ip_address", pattern: IP_ADDRESS, hold: false },
|
|
5991
|
+
{
|
|
5992
|
+
category: "encoded_or_high_entropy",
|
|
5993
|
+
pattern: SHORT_PADDED_BASE64,
|
|
5994
|
+
hold: true
|
|
5995
|
+
},
|
|
5996
|
+
{
|
|
5997
|
+
category: "encoded_or_high_entropy",
|
|
5998
|
+
pattern: SHORT_MIXED_OPAQUE,
|
|
5999
|
+
hold: true
|
|
6000
|
+
},
|
|
6001
|
+
{
|
|
6002
|
+
category: "credential",
|
|
6003
|
+
pattern: SHORT_CONTEXTUAL_CREDENTIAL,
|
|
6004
|
+
hold: false
|
|
6005
|
+
},
|
|
6006
|
+
{ category: "encoded_or_high_entropy", pattern: HIGH_ENTROPY, hold: true },
|
|
6007
|
+
{ category: "path", pattern: FILE_PATH, hold: false },
|
|
6008
|
+
{ category: "hostname", pattern: INTERNAL_HOSTNAME, hold: false },
|
|
6009
|
+
{ category: "hostname", pattern: HOSTNAME, hold: false },
|
|
6010
|
+
{ category: "identifier", pattern: IDENTIFIER, hold: false },
|
|
6011
|
+
{ category: "code_block", pattern: CODE_BLOCK, hold: true }
|
|
6012
|
+
];
|
|
6013
|
+
function normalizePrivateLessonText(value) {
|
|
6014
|
+
return value.normalize("NFC").replace(/\r\n?/gu, "\n").replace(/[\t ]+/gu, " ").replace(/ *\n */gu, "\n").replace(/\n{3,}/gu, "\n\n").trim();
|
|
6015
|
+
}
|
|
6016
|
+
function redactPrivateLessonText(value) {
|
|
6017
|
+
let text = normalizePrivateLessonText(value);
|
|
6018
|
+
const existingMarkers = [];
|
|
6019
|
+
text = text.replace(EXISTING_REDACTION_MARKER, (marker) => {
|
|
6020
|
+
const index = existingMarkers.push(marker) - 1;
|
|
6021
|
+
return `\uFFF0${index}\uFFF1`;
|
|
6022
|
+
});
|
|
6023
|
+
const counts = {};
|
|
6024
|
+
const held = /* @__PURE__ */ new Set();
|
|
6025
|
+
for (const replacement of REPLACEMENTS) {
|
|
6026
|
+
replacement.pattern.lastIndex = 0;
|
|
6027
|
+
let matches = 0;
|
|
6028
|
+
text = text.replace(replacement.pattern, () => {
|
|
6029
|
+
matches += 1;
|
|
6030
|
+
return `[redacted:${replacement.category}]`;
|
|
6031
|
+
});
|
|
6032
|
+
if (matches > 0) {
|
|
6033
|
+
counts[replacement.category] = (counts[replacement.category] ?? 0) + matches;
|
|
6034
|
+
if (replacement.hold) held.add(replacement.category);
|
|
6035
|
+
}
|
|
6036
|
+
}
|
|
6037
|
+
text = text.replace(/\uFFF0(\d+)\uFFF1/gu, (_, index) => {
|
|
6038
|
+
return existingMarkers[Number(index)] ?? "[redacted:identifier]";
|
|
6039
|
+
});
|
|
6040
|
+
return {
|
|
6041
|
+
text: normalizePrivateLessonText(text),
|
|
6042
|
+
counts: privateLessonRedactionCountsSchema.parse(counts),
|
|
6043
|
+
held_reasons: [...held].sort()
|
|
6044
|
+
};
|
|
6045
|
+
}
|
|
6046
|
+
function preparePrivateLessonCandidate(input) {
|
|
6047
|
+
input = privateLessonPreparationInputSchema.parse(input);
|
|
6048
|
+
const redactedParts = [
|
|
6049
|
+
redactPrivateLessonText(input.generalized_lesson ?? ""),
|
|
6050
|
+
...(input.stable_conditions ?? []).map(redactPrivateLessonText)
|
|
6051
|
+
];
|
|
6052
|
+
const tagParts = (input.tags ?? []).map(inspectPrivateLessonTag);
|
|
6053
|
+
const counts = {};
|
|
6054
|
+
const held = /* @__PURE__ */ new Set();
|
|
6055
|
+
for (const part of [...redactedParts, ...tagParts]) {
|
|
6056
|
+
for (const [category, count] of Object.entries(part.counts)) {
|
|
6057
|
+
const key = category;
|
|
6058
|
+
counts[key] = Math.min(999, (counts[key] ?? 0) + Number(count));
|
|
6059
|
+
}
|
|
6060
|
+
for (const reason of part.held_reasons) held.add(reason);
|
|
6061
|
+
}
|
|
6062
|
+
const lesson = redactedParts[0]?.text || null;
|
|
6063
|
+
const candidate = privateLessonCanonicalPayloadSchema.parse({
|
|
6064
|
+
contract_version: PRIVATE_LESSON_CONTRACT_VERSION,
|
|
6065
|
+
redaction_version: PRIVATE_LESSON_REDACTION_VERSION,
|
|
6066
|
+
event_type: input.event_type,
|
|
6067
|
+
tier: lesson ? "bounded_redacted_prose" : "structured_metadata",
|
|
6068
|
+
generalized_lesson: lesson,
|
|
6069
|
+
stable_conditions: redactedParts.slice(1).map((part) => part.text).filter(Boolean),
|
|
6070
|
+
tags: [...new Set(tagParts.flatMap((part) => part.tag ?? []))].sort().slice(0, 12),
|
|
6071
|
+
correlation_ids: [
|
|
6072
|
+
...new Set((input.correlation_ids ?? []).map(pseudonymizeCorrelationId))
|
|
6073
|
+
].sort().slice(0, 8),
|
|
6074
|
+
redaction_counts: counts,
|
|
6075
|
+
evidence_hashes: [...new Set(input.evidence_hashes ?? [])].sort().slice(0, 16)
|
|
6076
|
+
});
|
|
6077
|
+
const canonicalPayload = canonicalPrivateLessonPayload(candidate);
|
|
6078
|
+
return {
|
|
6079
|
+
status: held.size > 0 ? "held_safety" : "ready",
|
|
6080
|
+
candidate,
|
|
6081
|
+
canonical_payload: canonicalPayload,
|
|
6082
|
+
payload_digest: hashValue(canonicalPayload),
|
|
6083
|
+
redactor_digest: privateLessonRedactorDigest(),
|
|
6084
|
+
held_reasons: [...held].sort()
|
|
6085
|
+
};
|
|
6086
|
+
}
|
|
6087
|
+
function canonicalPrivateLessonPayload(input) {
|
|
6088
|
+
const parsed = privateLessonCanonicalPayloadSchema.parse(input);
|
|
6089
|
+
const canonical = canonicalJson(parsed);
|
|
6090
|
+
assertPrivateLessonPayloadSize(canonical);
|
|
6091
|
+
return canonical;
|
|
6092
|
+
}
|
|
6093
|
+
function computePrivateLessonRedactorDigest() {
|
|
6094
|
+
return hashValue(
|
|
6095
|
+
canonicalJson({
|
|
6096
|
+
version: PRIVATE_LESSON_REDACTION_VERSION,
|
|
6097
|
+
categories: privateLessonRedactionCategorySchema.options,
|
|
6098
|
+
normalization: "unicode-nfc-lf-space-v1",
|
|
6099
|
+
metadata: {
|
|
6100
|
+
correlation_ids: "domain-separated-sha256-v1",
|
|
6101
|
+
tag_syntax: PRIVATE_LESSON_TAG_PATTERN.source,
|
|
6102
|
+
tag_safety: "redaction-plus-identity-hints-v1",
|
|
6103
|
+
tag_identity_hints: [...PRIVATE_LESSON_TAG_IDENTITY_HINTS].sort(),
|
|
6104
|
+
tag_aliases: PRIVATE_LESSON_SAFE_TAG_ALIASES
|
|
6105
|
+
},
|
|
6106
|
+
rules: REPLACEMENTS.map(({ category, pattern, hold }) => ({
|
|
6107
|
+
category,
|
|
6108
|
+
pattern: pattern.source,
|
|
6109
|
+
flags: pattern.flags,
|
|
6110
|
+
hold
|
|
6111
|
+
}))
|
|
6112
|
+
})
|
|
6113
|
+
);
|
|
6114
|
+
}
|
|
6115
|
+
function inspectPrivateLessonTag(value) {
|
|
6116
|
+
const normalized = normalizePrivateLessonText(value).toLowerCase();
|
|
6117
|
+
const aliased = PRIVATE_LESSON_SAFE_TAG_ALIASES[normalized] ?? normalized;
|
|
6118
|
+
const redacted = redactPrivateLessonText(aliased);
|
|
6119
|
+
const counts = { ...redacted.counts };
|
|
6120
|
+
const held = /* @__PURE__ */ new Set([
|
|
6121
|
+
...redacted.held_reasons,
|
|
6122
|
+
...Object.keys(redacted.counts)
|
|
6123
|
+
]);
|
|
6124
|
+
const identityHints = new Set(
|
|
6125
|
+
aliased.split(/[-_]/u).filter((segment) => PRIVATE_LESSON_TAG_IDENTITY_HINTS.has(segment))
|
|
6126
|
+
);
|
|
6127
|
+
if (!privateLessonTagSyntaxSchema.safeParse(aliased).success || redacted.text !== aliased || identityHints.size >= 2) {
|
|
6128
|
+
if (held.size === 0) {
|
|
6129
|
+
counts.identifier = Math.min(999, (counts.identifier ?? 0) + 1);
|
|
6130
|
+
held.add("identifier");
|
|
6131
|
+
}
|
|
6132
|
+
return {
|
|
6133
|
+
tag: null,
|
|
6134
|
+
counts,
|
|
6135
|
+
held_reasons: [...held].sort()
|
|
6136
|
+
};
|
|
6137
|
+
}
|
|
6138
|
+
return {
|
|
6139
|
+
tag: aliased,
|
|
6140
|
+
counts: {},
|
|
6141
|
+
held_reasons: []
|
|
6142
|
+
};
|
|
6143
|
+
}
|
|
6144
|
+
function pseudonymizeCorrelationId(value) {
|
|
6145
|
+
const digest = hashValue(
|
|
6146
|
+
canonicalJson({
|
|
6147
|
+
domain: "remembrance-private-lesson-correlation-v1",
|
|
6148
|
+
value: normalizePrivateLessonText(value)
|
|
6149
|
+
})
|
|
6150
|
+
).slice("sha256:".length);
|
|
6151
|
+
return `corr_${digest.slice(0, 48)}`;
|
|
6152
|
+
}
|
|
6153
|
+
var CURRENT_PRIVATE_LESSON_REDACTOR_DIGEST = computePrivateLessonRedactorDigest();
|
|
6154
|
+
var PRIVATE_LESSON_SUPPORTED_REDACTOR_PROFILES = Object.freeze({
|
|
6155
|
+
[PRIVATE_LESSON_REDACTION_VERSION]: Object.freeze({
|
|
6156
|
+
[CURRENT_PRIVATE_LESSON_REDACTOR_DIGEST]: Object.freeze({
|
|
6157
|
+
version: PRIVATE_LESSON_REDACTION_VERSION,
|
|
6158
|
+
digest: CURRENT_PRIVATE_LESSON_REDACTOR_DIGEST,
|
|
6159
|
+
normalization: "unicode-nfc-lf-space-v1"
|
|
6160
|
+
})
|
|
6161
|
+
})
|
|
6162
|
+
});
|
|
6163
|
+
var privateLessonRedactionProfileErrorCodeSchema = external_exports.enum([
|
|
6164
|
+
"unsupported_redaction_version",
|
|
6165
|
+
"unsupported_redactor_digest"
|
|
6166
|
+
]);
|
|
6167
|
+
var PrivateLessonRedactionProfileError = class extends Error {
|
|
6168
|
+
constructor(code) {
|
|
6169
|
+
super(
|
|
6170
|
+
code === "unsupported_redaction_version" ? "Private lesson redaction version is unsupported." : "Private lesson redactor digest is unsupported."
|
|
6171
|
+
);
|
|
6172
|
+
this.code = code;
|
|
6173
|
+
this.name = "PrivateLessonRedactionProfileError";
|
|
6174
|
+
}
|
|
6175
|
+
code;
|
|
6176
|
+
};
|
|
6177
|
+
function privateLessonRedactorDigest() {
|
|
6178
|
+
return CURRENT_PRIVATE_LESSON_REDACTOR_DIGEST;
|
|
6179
|
+
}
|
|
6180
|
+
function privateLessonRedactorProfile(version, digest) {
|
|
6181
|
+
if (typeof version !== "string" || !PRIVATE_LESSON_SUPPORTED_REDACTION_VERSIONS.includes(
|
|
6182
|
+
version
|
|
6183
|
+
)) {
|
|
6184
|
+
throw new PrivateLessonRedactionProfileError(
|
|
6185
|
+
"unsupported_redaction_version"
|
|
6186
|
+
);
|
|
6187
|
+
}
|
|
6188
|
+
if (typeof digest !== "string") {
|
|
6189
|
+
throw new PrivateLessonRedactionProfileError("unsupported_redactor_digest");
|
|
6190
|
+
}
|
|
6191
|
+
const profile = PRIVATE_LESSON_SUPPORTED_REDACTOR_PROFILES[version]?.[digest];
|
|
6192
|
+
if (!profile) {
|
|
6193
|
+
throw new PrivateLessonRedactionProfileError("unsupported_redactor_digest");
|
|
6194
|
+
}
|
|
6195
|
+
return profile;
|
|
6196
|
+
}
|
|
6197
|
+
function canonicalPrivateLessonHoldEvent(input) {
|
|
6198
|
+
return canonicalJson(privateLessonHoldEventPayloadSchema.parse(input));
|
|
6199
|
+
}
|
|
6200
|
+
function privateLessonHoldEventDigest(input) {
|
|
6201
|
+
return hashValue(canonicalPrivateLessonHoldEvent(input));
|
|
6202
|
+
}
|
|
6203
|
+
function privateLessonPolicyDigest(input) {
|
|
6204
|
+
return hashValue(
|
|
6205
|
+
canonicalJson(privateLessonPolicyPayloadSchema.parse(input))
|
|
6206
|
+
);
|
|
6207
|
+
}
|
|
6208
|
+
function privateLessonSignaturePayload(purpose, canonicalPayload) {
|
|
6209
|
+
return canonicalJson({
|
|
6210
|
+
domain: "remembrance-signature-v1",
|
|
6211
|
+
purpose,
|
|
6212
|
+
payload: canonicalPayload
|
|
6213
|
+
});
|
|
6214
|
+
}
|
|
6215
|
+
function verifyPrivateLessonSignedEnvelope(args) {
|
|
6216
|
+
if (args.kind !== "policy" && args.policyFloor !== void 0) {
|
|
6217
|
+
throw new Error(
|
|
6218
|
+
"A private lesson policy trust floor applies only to policies."
|
|
6219
|
+
);
|
|
6220
|
+
}
|
|
6221
|
+
const profile = privateLessonSignedEnvelopeProfileFields(args.envelope);
|
|
6222
|
+
if (profile.version !== void 0 || profile.digest !== void 0) {
|
|
6223
|
+
privateLessonRedactorProfile(profile.version, profile.digest);
|
|
6224
|
+
}
|
|
6225
|
+
const schema = args.kind === "policy" ? signedPrivateLessonPolicySchema : args.kind === "receipt" ? signedPrivateLessonReceiptSchema : signedPrivateLessonHoldReceiptSchema;
|
|
6226
|
+
const envelope = schema.parse(args.envelope);
|
|
6227
|
+
const canonical = canonicalJson(envelope.payload);
|
|
6228
|
+
if (canonical !== envelope.canonical_payload) {
|
|
6229
|
+
throw new Error("Private lesson signed payload is not canonical.");
|
|
6230
|
+
}
|
|
6231
|
+
const keys = privateLessonVerificationKeySetSchema.parse(args.keySet);
|
|
6232
|
+
const jwk = keys.keys.find((key) => key.kid === envelope.key_id);
|
|
6233
|
+
if (!jwk) {
|
|
6234
|
+
throw new Error(
|
|
6235
|
+
`Private lesson verification key is unavailable: ${envelope.key_id}`
|
|
6236
|
+
);
|
|
6237
|
+
}
|
|
6238
|
+
const purpose = args.kind === "policy" ? "remembrance-private-lesson-policy" : args.kind === "receipt" ? "remembrance-private-lesson-receipt" : "remembrance-private-lesson-hold-receipt";
|
|
6239
|
+
const signatureValid = verify(
|
|
6240
|
+
null,
|
|
6241
|
+
Buffer.from(privateLessonSignaturePayload(purpose, canonical)),
|
|
6242
|
+
createPublicKey({
|
|
6243
|
+
key: { kty: "OKP", crv: "Ed25519", x: jwk.x },
|
|
6244
|
+
format: "jwk"
|
|
6245
|
+
}),
|
|
6246
|
+
Buffer.from(envelope.signature, "base64url")
|
|
6247
|
+
);
|
|
6248
|
+
if (!signatureValid) {
|
|
6249
|
+
throw new Error("Private lesson signature is invalid.");
|
|
6250
|
+
}
|
|
6251
|
+
if (args.kind === "policy") {
|
|
6252
|
+
const policy = envelope;
|
|
6253
|
+
privateLessonRedactorProfile(
|
|
6254
|
+
policy.payload.redaction_version,
|
|
6255
|
+
policy.payload.redactor_digest
|
|
6256
|
+
);
|
|
6257
|
+
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
6258
|
+
if (new Date(policy.payload.expires_at) <= now) {
|
|
6259
|
+
throw new Error("Private lesson policy is expired.");
|
|
6260
|
+
}
|
|
6261
|
+
if (new Date(policy.payload.issued_at) > now) {
|
|
6262
|
+
throw new Error("Private lesson policy is not active yet.");
|
|
6263
|
+
}
|
|
6264
|
+
if (args.policyFloor !== void 0) {
|
|
6265
|
+
const floor = privateLessonPolicyTrustFloorSchema.parse(args.policyFloor);
|
|
6266
|
+
if (policy.payload.organization_audience_hash !== floor.organization_audience_hash) {
|
|
6267
|
+
throw new Error(
|
|
6268
|
+
"Private lesson policy audience does not match the trusted organization."
|
|
6269
|
+
);
|
|
6270
|
+
}
|
|
6271
|
+
if (policy.payload.policy_sequence < floor.policy_sequence) {
|
|
6272
|
+
throw new Error("Private lesson policy is stale.");
|
|
6273
|
+
}
|
|
6274
|
+
if (policy.payload.policy_sequence === floor.policy_sequence && privateLessonPolicyDigest(policy.payload) !== floor.policy_digest) {
|
|
6275
|
+
throw new Error(
|
|
6276
|
+
"Private lesson policy conflicts with the trusted digest at the same sequence."
|
|
6277
|
+
);
|
|
6278
|
+
}
|
|
6279
|
+
}
|
|
6280
|
+
} else {
|
|
6281
|
+
privateLessonRedactorProfile(
|
|
6282
|
+
envelope.payload.redaction_version,
|
|
6283
|
+
envelope.payload.redactor_digest
|
|
6284
|
+
);
|
|
6285
|
+
}
|
|
6286
|
+
return envelope;
|
|
6287
|
+
}
|
|
6288
|
+
function privateLessonSignedEnvelopeProfileFields(input) {
|
|
6289
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
6290
|
+
return { version: void 0, digest: void 0 };
|
|
6291
|
+
}
|
|
6292
|
+
const envelope = input;
|
|
6293
|
+
if (!envelope.payload || typeof envelope.payload !== "object" || Array.isArray(envelope.payload)) {
|
|
6294
|
+
return { version: void 0, digest: void 0 };
|
|
6295
|
+
}
|
|
6296
|
+
const payload = envelope.payload;
|
|
6297
|
+
return {
|
|
6298
|
+
version: payload.redaction_version,
|
|
6299
|
+
digest: payload.redactor_digest
|
|
6300
|
+
};
|
|
6301
|
+
}
|
|
6302
|
+
function assertPrivateLessonPayloadSize(canonicalPayload) {
|
|
6303
|
+
if (Buffer.byteLength(canonicalPayload, "utf8") > PRIVATE_LESSON_MAX_PAYLOAD_BYTES) {
|
|
6304
|
+
throw new Error(
|
|
6305
|
+
`Private lesson payload exceeds ${PRIVATE_LESSON_MAX_PAYLOAD_BYTES} bytes.`
|
|
6306
|
+
);
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
6309
|
+
function assertNetworkSafeLessonText(value, ctx) {
|
|
6310
|
+
const forbidden = [
|
|
6311
|
+
[
|
|
6312
|
+
ZERO_WIDTH_OR_BIDI,
|
|
6313
|
+
"Unicode direction or zero-width controls are forbidden"
|
|
6314
|
+
],
|
|
6315
|
+
[CODE_BLOCK, "Code blocks are forbidden"],
|
|
6316
|
+
[STACK_TRACE, "Raw stack traces are forbidden"],
|
|
6317
|
+
[SCREENSHOT, "Screenshot references are forbidden"],
|
|
6318
|
+
[PUBLIC_URL, "URLs are forbidden"],
|
|
6319
|
+
[CONNECTION_STRING, "Connection strings are forbidden"],
|
|
6320
|
+
[COOKIE, "Cookies are forbidden"],
|
|
6321
|
+
[EMAIL, "Emails are forbidden"],
|
|
6322
|
+
[IP_ADDRESS, "IP addresses are forbidden"],
|
|
6323
|
+
[INTERNAL_HOSTNAME, "Hostnames are forbidden"],
|
|
6324
|
+
[HOSTNAME, "Hostnames are forbidden"],
|
|
6325
|
+
[FILE_PATH, "File paths are forbidden"],
|
|
6326
|
+
[IDENTIFIER, "Private identifiers are forbidden"],
|
|
6327
|
+
[JWT, "JWTs are forbidden"],
|
|
6328
|
+
[CREDENTIAL, "Credentials are forbidden"],
|
|
6329
|
+
[SECRET, "Secrets are forbidden"],
|
|
6330
|
+
[SHORT_PADDED_BASE64, "Encoded or high-entropy values are forbidden"],
|
|
6331
|
+
[SHORT_MIXED_OPAQUE, "Encoded or high-entropy values are forbidden"],
|
|
6332
|
+
[SHORT_CONTEXTUAL_CREDENTIAL, "Credentials are forbidden"],
|
|
6333
|
+
[HIGH_ENTROPY, "Encoded or high-entropy values are forbidden"]
|
|
6334
|
+
];
|
|
6335
|
+
for (const [pattern, message] of forbidden) {
|
|
6336
|
+
pattern.lastIndex = 0;
|
|
6337
|
+
if (pattern.test(value)) {
|
|
6338
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, message });
|
|
6339
|
+
}
|
|
6340
|
+
}
|
|
6341
|
+
}
|
|
6342
|
+
|
|
5569
6343
|
// ../core/src/remembrance-mcp-policy.ts
|
|
5570
6344
|
var REMEMBRANCE_MCP_READ_TOOLS = [
|
|
5571
6345
|
"get_connection_status",
|
|
@@ -5574,6 +6348,7 @@ var REMEMBRANCE_MCP_READ_TOOLS = [
|
|
|
5574
6348
|
"list_skills",
|
|
5575
6349
|
"invoke_skill",
|
|
5576
6350
|
"get_effective_preferences",
|
|
6351
|
+
"get_private_lesson_policy",
|
|
5577
6352
|
"get_skill",
|
|
5578
6353
|
"get_resource",
|
|
5579
6354
|
"get_value_proof"
|
|
@@ -5589,7 +6364,8 @@ var REMEMBRANCE_MCP_PRIVATE_CONTRIBUTION_TOOLS = [
|
|
|
5589
6364
|
"propose_private_skill",
|
|
5590
6365
|
"submit_suggestion",
|
|
5591
6366
|
"record_preference",
|
|
5592
|
-
"link_current_installation"
|
|
6367
|
+
"link_current_installation",
|
|
6368
|
+
"submit_private_lesson_candidate"
|
|
5593
6369
|
];
|
|
5594
6370
|
var REMEMBRANCE_MCP_OPTIONAL_SHARED_CONTRIBUTION_TOOLS = [
|
|
5595
6371
|
"propose_skill_idea",
|
|
@@ -5600,7 +6376,11 @@ var REMEMBRANCE_MCP_OPTIONAL_SHARED_CONTRIBUTION_TOOLS = [
|
|
|
5600
6376
|
];
|
|
5601
6377
|
var REMEMBRANCE_MCP_LOCAL_ONLY_TOOLS = [
|
|
5602
6378
|
"bootstrap_agent_identity",
|
|
5603
|
-
"queue_private_skill_import"
|
|
6379
|
+
"queue_private_skill_import",
|
|
6380
|
+
"prepare_private_lesson_candidate",
|
|
6381
|
+
"inspect_private_lesson_outbox",
|
|
6382
|
+
"retry_private_lesson_candidate",
|
|
6383
|
+
"delete_private_lesson_candidate"
|
|
5604
6384
|
];
|
|
5605
6385
|
var REMEMBRANCE_MCP_RECOMMENDED_ORG_TOOLS = [
|
|
5606
6386
|
...REMEMBRANCE_MCP_READ_TOOLS,
|
|
@@ -6529,12 +7309,36 @@ var verifyRequestSchema = external_exports.object({
|
|
|
6529
7309
|
});
|
|
6530
7310
|
var attestationChallengeRequestSchema = external_exports.object({
|
|
6531
7311
|
provider: attestationProviderSchema.exclude(["org_api_key"]),
|
|
6532
|
-
source_type: external_exports.enum(["remembrance", "resource_review"]),
|
|
7312
|
+
source_type: external_exports.enum(["remembrance", "resource_review", "private_lesson"]),
|
|
6533
7313
|
agent_id: boundedString(MAX_SHORT_TEXT_LENGTH).optional(),
|
|
6534
7314
|
subject: boundedString(MAX_SHORT_TEXT_LENGTH).optional(),
|
|
6535
7315
|
skill_slug: boundedString(MAX_SHORT_TEXT_LENGTH).optional(),
|
|
6536
7316
|
resource_slug: boundedString(MAX_SHORT_TEXT_LENGTH).optional(),
|
|
6537
7317
|
evidence_hash: boundedString(MAX_SHORT_TEXT_LENGTH),
|
|
7318
|
+
private_lesson: external_exports.object({
|
|
7319
|
+
key_id: boundedString(MAX_SHORT_TEXT_LENGTH),
|
|
7320
|
+
record_type: external_exports.enum(["candidate", "held_safety_event"]).optional(),
|
|
7321
|
+
organization_audience_hash: external_exports.string().regex(/^sha256:[a-f0-9]{64}$/),
|
|
7322
|
+
payload_digest: external_exports.string().regex(/^sha256:[a-f0-9]{64}$/).optional(),
|
|
7323
|
+
event_digest: external_exports.string().regex(/^sha256:[a-f0-9]{64}$/).optional(),
|
|
7324
|
+
policy_digest: external_exports.string().regex(/^sha256:[a-f0-9]{64}$/),
|
|
7325
|
+
redactor_digest: external_exports.string().regex(/^sha256:[a-f0-9]{64}$/)
|
|
7326
|
+
}).strict().superRefine((binding, ctx) => {
|
|
7327
|
+
if ((binding.record_type ?? "candidate") === "candidate" && (!binding.payload_digest || binding.event_digest)) {
|
|
7328
|
+
ctx.addIssue({
|
|
7329
|
+
code: external_exports.ZodIssueCode.custom,
|
|
7330
|
+
message: "candidate binding requires only payload_digest",
|
|
7331
|
+
path: ["payload_digest"]
|
|
7332
|
+
});
|
|
7333
|
+
}
|
|
7334
|
+
if (binding.record_type === "held_safety_event" && (!binding.event_digest || binding.payload_digest)) {
|
|
7335
|
+
ctx.addIssue({
|
|
7336
|
+
code: external_exports.ZodIssueCode.custom,
|
|
7337
|
+
message: "held_safety_event binding requires only event_digest",
|
|
7338
|
+
path: ["event_digest"]
|
|
7339
|
+
});
|
|
7340
|
+
}
|
|
7341
|
+
}).optional(),
|
|
6538
7342
|
expires_in_seconds: external_exports.number().int().min(30).max(600).default(300)
|
|
6539
7343
|
}).superRefine((value, ctx) => {
|
|
6540
7344
|
if (!value.agent_id) {
|
|
@@ -6558,6 +7362,20 @@ var attestationChallengeRequestSchema = external_exports.object({
|
|
|
6558
7362
|
path: ["resource_slug"]
|
|
6559
7363
|
});
|
|
6560
7364
|
}
|
|
7365
|
+
if (value.source_type === "private_lesson" && !value.private_lesson) {
|
|
7366
|
+
ctx.addIssue({
|
|
7367
|
+
code: external_exports.ZodIssueCode.custom,
|
|
7368
|
+
message: "private_lesson binding is required for private lesson attestations",
|
|
7369
|
+
path: ["private_lesson"]
|
|
7370
|
+
});
|
|
7371
|
+
}
|
|
7372
|
+
if (value.source_type !== "private_lesson" && value.private_lesson !== void 0) {
|
|
7373
|
+
ctx.addIssue({
|
|
7374
|
+
code: external_exports.ZodIssueCode.custom,
|
|
7375
|
+
message: "private_lesson binding is only valid for private lesson attestations",
|
|
7376
|
+
path: ["private_lesson"]
|
|
7377
|
+
});
|
|
7378
|
+
}
|
|
6561
7379
|
});
|
|
6562
7380
|
var attestationKeyRegistrationRequestSchema = external_exports.object({
|
|
6563
7381
|
provider: attestationProviderSchema.exclude(["org_api_key"]),
|
|
@@ -6766,7 +7584,7 @@ var seedSkills = [
|
|
|
6766
7584
|
summary: "Operational setup and troubleshooting workflow for Remembrance across Claude Code, Codex, OpenClaw, Cursor, Gemini, MCP, REST, skill-only installs, enterprise keys, and local agent identity.",
|
|
6767
7585
|
status: "active",
|
|
6768
7586
|
visibility: "public",
|
|
6769
|
-
version: "0.1.
|
|
7587
|
+
version: "0.1.23",
|
|
6770
7588
|
domains: ["agent-skills", "mcp", "resource-discovery"],
|
|
6771
7589
|
tags: [
|
|
6772
7590
|
"remembrance",
|
|
@@ -6817,7 +7635,7 @@ var seedSkills = [
|
|
|
6817
7635
|
"gemini",
|
|
6818
7636
|
"rest"
|
|
6819
7637
|
],
|
|
6820
|
-
version: "0.1.
|
|
7638
|
+
version: "0.1.23",
|
|
6821
7639
|
status: "active",
|
|
6822
7640
|
visibility: "public",
|
|
6823
7641
|
providers: ["codex", "claude", "cursor", "openclaw", "generic"],
|
|
@@ -7155,6 +7973,68 @@ manual override needs a process environment or HTTP header credential. A Codex
|
|
|
7155
7973
|
tenant/privacy-policy denial is enforced by Codex before the request reaches
|
|
7156
7974
|
Remembrance; do not classify it as a Remembrance rejection.
|
|
7157
7975
|
|
|
7976
|
+
### Organization-private lesson autopilot
|
|
7977
|
+
|
|
7978
|
+
Routine organization lessons use one narrow, reviewable write instead of a
|
|
7979
|
+
broad remembrance payload. The local plugin first calls
|
|
7980
|
+
\`prepare_private_lesson_candidate\`. That tool generalizes, canonicalizes, and
|
|
7981
|
+
redacts a failure, correction, or reusable workflow lesson in memory; encrypts
|
|
7982
|
+
only the canonical safe record in a local outbox; and returns a draft ID. It
|
|
7983
|
+
never writes the original input to disk, logs, telemetry, or error output. Tags
|
|
7984
|
+
use an open, bounded lowercase slug vocabulary; new technical terms survive,
|
|
7985
|
+
while malformed or privacy-sensitive tags hold the draft instead of being
|
|
7986
|
+
silently removed.
|
|
7987
|
+
|
|
7988
|
+
The plugin then calls \`submit_private_lesson_candidate\` with that draft ID.
|
|
7989
|
+
This is the only host-visible network action in the flow. Approve or persist
|
|
7990
|
+
approval for this exact action, not every Remembrance write. The server derives
|
|
7991
|
+
the organization and private visibility from the authenticated key, verifies a
|
|
7992
|
+
signed organization policy plus purpose-bound attestation, and returns a signed
|
|
7993
|
+
content-free receipt. The endpoint can never create or automatically propagate
|
|
7994
|
+
public content.
|
|
7995
|
+
|
|
7996
|
+
If the host denies the action, no candidate content was sent. The encrypted
|
|
7997
|
+
draft remains on the device in \`awaiting_authorization\`; do not retry it
|
|
7998
|
+
through REST, hosted MCP, or another transport. Timeouts, 429s, and 5xx
|
|
7999
|
+
responses retry with bounded backoff during later plugin lifecycles. A 401,
|
|
8000
|
+
403, policy change, or validation failure remains held for explicit repair.
|
|
8001
|
+
Drafts never expire or auto-delete. Inspect, retry, or explicitly delete one
|
|
8002
|
+
with the local private-lesson tools; deletion requires confirmation.
|
|
8003
|
+
|
|
8004
|
+
The signed organization policy pins \`private-lesson-redaction-v2\` and its exact
|
|
8005
|
+
supported redactor digest. If either is unsupported, the finalized
|
|
8006
|
+
\`private-lesson-outbox-v1\` record moves to terminal \`superseded_redactor\`.
|
|
8007
|
+
Terminal drafts are never retried, re-redacted, expired, or automatically
|
|
8008
|
+
deleted, and they continue to count toward the 64 MiB outbox ceiling. Outbox
|
|
8009
|
+
inspection and the connection doctor report terminal count, retained bytes,
|
|
8010
|
+
reason, and explicit deletion guidance without returning lesson content or a
|
|
8011
|
+
local path.
|
|
8012
|
+
|
|
8013
|
+
When health reporting is enabled, a held draft may submit a content-free
|
|
8014
|
+
\`held_safety_event\` through the same exact action. It contains only the event
|
|
8015
|
+
type, held category counts, contract/redactor profile, event and policy
|
|
8016
|
+
digests, idempotency key, and a purpose-bound attestation. It never contains
|
|
8017
|
+
lesson prose, conditions, tags, correlations, evidence hashes, a candidate
|
|
8018
|
+
digest, paths, or draft content. Hold telemetry cannot enter verification,
|
|
8019
|
+
review, topology, propagation, or skill materialization. Set
|
|
8020
|
+
\`REMEMBRANCE_HEALTH_REPORTING=0\` to disable this optional report without
|
|
8021
|
+
affecting queries or retained drafts; the organization kill switch disables
|
|
8022
|
+
the entire private-lesson lane.
|
|
8023
|
+
|
|
8024
|
+
Structured metadata and bounded redacted prose are enabled by default for an
|
|
8025
|
+
authenticated organization. Raw traces, code blocks, URLs, attachments,
|
|
8026
|
+
secrets, paths, identifiers, screenshots, encoded/high-entropy content, and
|
|
8027
|
+
ambiguous material remain local. Rich content cannot be sent merely because a
|
|
8028
|
+
user confirms it; first generalize it into the safe schema. Organization admins
|
|
8029
|
+
can pause the lane or select metadata-only capture under **Dashboard > Settings
|
|
8030
|
+
> Private lesson automation**.
|
|
8031
|
+
|
|
8032
|
+
Hosted-only MCP and REST clients report \`auto_capture_supported: false\`
|
|
8033
|
+
because they cannot guarantee durable local retention. They must implement the
|
|
8034
|
+
same two-stage boundary locally: canonicalize and durably retain the safe draft,
|
|
8035
|
+
then invoke only \`POST /api/v1/agent/private-lessons\`. Querying and the main
|
|
8036
|
+
agent task remain available when capture or submission is blocked.
|
|
8037
|
+
|
|
7158
8038
|
### Approve private repository contributions in managed Codex
|
|
7159
8039
|
|
|
7160
8040
|
An API key authorizes Remembrance; it does not authorize Codex to export
|
|
@@ -8198,8 +9078,8 @@ POST https://remembrance.dev/api/v1/resources/reviews
|
|
|
8198
9078
|
|
|
8199
9079
|
// ../core/src/skill-value.ts
|
|
8200
9080
|
import {
|
|
8201
|
-
createPublicKey,
|
|
8202
|
-
verify
|
|
9081
|
+
createPublicKey as createPublicKey2,
|
|
9082
|
+
verify as verify2
|
|
8203
9083
|
} from "node:crypto";
|
|
8204
9084
|
var tokenSavingsRangeSchema = external_exports.object({
|
|
8205
9085
|
low: external_exports.number().int(),
|
|
@@ -8340,10 +9220,10 @@ function verifySignedValueProof(proofInput, keySetInput, now = /* @__PURE__ */ n
|
|
|
8340
9220
|
"malformed"
|
|
8341
9221
|
);
|
|
8342
9222
|
}
|
|
8343
|
-
const verified =
|
|
9223
|
+
const verified = verify2(
|
|
8344
9224
|
null,
|
|
8345
9225
|
Buffer.from(canonicalJson(proof.payload)),
|
|
8346
|
-
|
|
9226
|
+
createPublicKey2({
|
|
8347
9227
|
key: { kty: "OKP", crv: "Ed25519", x: jwk.x },
|
|
8348
9228
|
format: "jwk"
|
|
8349
9229
|
}),
|
|
@@ -8494,9 +9374,9 @@ var verifierOutputSchema = external_exports.object({
|
|
|
8494
9374
|
});
|
|
8495
9375
|
|
|
8496
9376
|
// src/server.ts
|
|
8497
|
-
import { existsSync as
|
|
8498
|
-
import { mkdir as
|
|
8499
|
-
import { dirname as
|
|
9377
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
9378
|
+
import { mkdir as mkdir3, writeFile as writeFile2 } from "node:fs/promises";
|
|
9379
|
+
import { dirname as dirname3 } from "node:path";
|
|
8500
9380
|
import {
|
|
8501
9381
|
createHash as createHash3,
|
|
8502
9382
|
createHmac,
|
|
@@ -9798,6 +10678,24 @@ var getBySlugSchema = external_exports.object({
|
|
|
9798
10678
|
var getValueProofSchema = external_exports.object({
|
|
9799
10679
|
id: external_exports.string().min(1).describe("Value proof id returned with potential_savings.")
|
|
9800
10680
|
});
|
|
10681
|
+
var privateLessonDraftReferenceSchema = external_exports.object({
|
|
10682
|
+
draft_id: external_exports.string().regex(/^pld_[A-Za-z0-9_-]{8,96}$/).describe(
|
|
10683
|
+
"Local private-lesson draft returned by prepare_private_lesson_candidate."
|
|
10684
|
+
)
|
|
10685
|
+
}).strict();
|
|
10686
|
+
var privateLessonInspectSchema = external_exports.object({
|
|
10687
|
+
draft_id: external_exports.string().regex(/^pld_[A-Za-z0-9_-]{8,96}$/).optional(),
|
|
10688
|
+
limit: external_exports.number().int().min(1).max(200).default(50)
|
|
10689
|
+
}).strict();
|
|
10690
|
+
var privateLessonDeleteSchema = privateLessonDraftReferenceSchema.extend({
|
|
10691
|
+
confirm: external_exports.literal(true).describe(
|
|
10692
|
+
"Must be true. Deletion is explicit and leaves a content-free tombstone."
|
|
10693
|
+
)
|
|
10694
|
+
});
|
|
10695
|
+
var privateLessonSubmissionToolSchema = external_exports.union([
|
|
10696
|
+
privateLessonDraftReferenceSchema,
|
|
10697
|
+
privateLessonEndpointSubmissionSchema
|
|
10698
|
+
]);
|
|
9801
10699
|
var bootstrapAgentIdentitySchema = external_exports.object({
|
|
9802
10700
|
subject: external_exports.string().min(1).optional().describe(
|
|
9803
10701
|
"Optional stable agent identity. Omit it to generate a private opaque subject from the new public-key fingerprint."
|
|
@@ -9867,9 +10765,16 @@ var toolDefinitions = [
|
|
|
9867
10765
|
"/api/v1/agent/preferences/effective",
|
|
9868
10766
|
effectivePreferencesRequestSchema
|
|
9869
10767
|
),
|
|
10768
|
+
tool(
|
|
10769
|
+
"get_private_lesson_policy",
|
|
10770
|
+
"Fetch and cryptographically verify the authenticated organization's signed private-lesson automation policy. The server response never exposes an organization id and public_fallback is always false. The local MCP augments this with secure outbox readiness; hosted-only clients report auto_capture_supported=false.",
|
|
10771
|
+
"/api/v1/agent/private-lesson-policy",
|
|
10772
|
+
external_exports.object({}).strict(),
|
|
10773
|
+
"GET"
|
|
10774
|
+
),
|
|
9870
10775
|
tool(
|
|
9871
10776
|
"record_preference",
|
|
9872
|
-
"Record a bounded, privacy-safe preference observation for the current verified installation. Built-in settings need only key and value; a custom setting also supplies its plain-language label, behavior, presentation/workflow/strategy_selection effect, prefer/avoid direction, and definition version. Send normalized settings and hashes only, never prompt text. A valid principal session is required.
|
|
10777
|
+
"Record a bounded, privacy-safe preference observation for the current verified installation. Built-in settings need only key and value; a custom setting also supplies its plain-language label, behavior, presentation/workflow/strategy_selection effect, prefer/avoid direction, and definition version. Send normalized settings and hashes only, never prompt text. A valid principal session is required. An explicit instruction governs the current task immediately. Known built-ins activate durably at once; custom settings remain pending until automatic normalization and validation approves them. Inferred observations activate only after Remembrance's consistency and confidence thresholds are met.",
|
|
9873
10778
|
"/api/v1/agent/preferences",
|
|
9874
10779
|
recordPreferenceRequestSchema
|
|
9875
10780
|
),
|
|
@@ -9911,6 +10816,36 @@ var toolDefinitions = [
|
|
|
9911
10816
|
"queue_private_skill_import",
|
|
9912
10817
|
organizationSkillHandoffRequestSchema
|
|
9913
10818
|
),
|
|
10819
|
+
localTool(
|
|
10820
|
+
"prepare_private_lesson_candidate",
|
|
10821
|
+
"Locally canonicalize, redact, validate, encrypt, and durably queue one compact organization-private failure, correction, or evidence lesson. This is an evidence lane, not a complete-skill transport: use propose_private_skill or propose_skill_idea for a complete reusable procedure, playbook, or actionable instructions. Tags use an open, bounded lowercase slug vocabulary; malformed or privacy-sensitive tags hold the draft instead of being dropped. Pre-redaction input remains memory-only and this tool performs no network request. The result returns a draft handle and never echoes lesson prose. Follow the returned next_action with submit_private_lesson_candidate; held_safety content remains local. An unsupported redactor version or digest becomes terminal superseded_redactor and is never retried or re-redacted.",
|
|
10822
|
+
"prepare_private_lesson_candidate",
|
|
10823
|
+
privateLessonPreparationInputSchema
|
|
10824
|
+
),
|
|
10825
|
+
localTool(
|
|
10826
|
+
"inspect_private_lesson_outbox",
|
|
10827
|
+
"Inspect content-free state, retained bytes, terminal reasons, deletion guidance, and retry metadata for encrypted local private-lesson drafts. This never returns lesson prose or canonical payload bytes. Terminal drafts remain retained and count toward the safety ceiling until explicitly deleted.",
|
|
10828
|
+
"inspect_private_lesson_outbox",
|
|
10829
|
+
privateLessonInspectSchema
|
|
10830
|
+
),
|
|
10831
|
+
tool(
|
|
10832
|
+
"submit_private_lesson_candidate",
|
|
10833
|
+
"Submit exactly one canonical, challenge-attested organization-private lesson candidate or one content-free held-safety telemetry event. Local plugin clients normally pass only the draft_id returned by prepare_private_lesson_candidate; raw or hosted clients pass the strict candidate-or-hold union. The action can never create or automatically promote public content. Held telemetry contains no lesson prose and can never enter verification, review, topology, or skill materialization. Exact replay is idempotent; host denial leaves the local draft untouched.",
|
|
10834
|
+
"/api/v1/agent/private-lessons",
|
|
10835
|
+
privateLessonSubmissionToolSchema
|
|
10836
|
+
),
|
|
10837
|
+
localTool(
|
|
10838
|
+
"retry_private_lesson_candidate",
|
|
10839
|
+
"Explicitly retry one retained private-lesson draft after authorization, policy, or transient service issues are resolved. It uses the same exact private submission action and never falls back to a public or alternate transport.",
|
|
10840
|
+
"retry_private_lesson_candidate",
|
|
10841
|
+
privateLessonDraftReferenceSchema
|
|
10842
|
+
),
|
|
10843
|
+
localTool(
|
|
10844
|
+
"delete_private_lesson_candidate",
|
|
10845
|
+
"Explicitly delete one selected local private-lesson draft. confirm=true is required. No automatic cleanup exists, and deletion leaves only a content-free tombstone.",
|
|
10846
|
+
"delete_private_lesson_candidate",
|
|
10847
|
+
privateLessonDeleteSchema
|
|
10848
|
+
),
|
|
9914
10849
|
tool(
|
|
9915
10850
|
"submit_feedback",
|
|
9916
10851
|
"Submit minimal post-use skill feedback. When the skill came from query_skills, pass its query_id and result_id to close the surfaced-to-use funnel. If the response includes next_step.submit_remembrance_payload, call submit_remembrance to promote substantive feedback to rich evidence. For self-corrections or CI/deploy/release misses, use submit_remembrance with type failure_report.",
|
|
@@ -9944,13 +10879,13 @@ var toolDefinitions = [
|
|
|
9944
10879
|
),
|
|
9945
10880
|
tool(
|
|
9946
10881
|
"propose_skill_idea",
|
|
9947
|
-
"Propose a missing reusable skill when query_skills has no useful result. THIS TOOL'S VISIBILITY IS NOT FIXED: an active organization API key keeps the candidate inside that organization's review queue, while intentionally calling without a key creates a PUBLIC candidate. A supplied invalid/inactive key fails with 401 and a valid key without submission:create fails with 403; neither failure creates a candidate. Prefer propose_private_skill for anything repository-derived, organization-specific, or otherwise not intended for the public registry because that endpoint structurally requires organization auth. Use propose_skill_idea only when a public candidate is an acceptable outcome. Check `visibility` (organization_private or public_candidate) and `owner_scope` in the successful response, and report where the candidate landed. Never remove or bypass an organization key to force public submission: submit privately, then use the reviewed public-propagation flow. Send repository-derived private instructions only when Remembrance is an organization-approved destination. If host policy blocks the export, report that nothing was sent, do not retry, and do not create a handoff unless an organization admin explicitly requests one.",
|
|
10882
|
+
"Propose a complete missing reusable skill when query_skills has no useful result. This full skill lane retains bounded markdown, public citations, snippets, metadata, and actionable instructions through normal safety and review; do not compress a finished skill into the narrow private-lesson contract. THIS TOOL'S VISIBILITY IS NOT FIXED: an active organization API key keeps the candidate inside that organization's review queue, while intentionally calling without a key creates a PUBLIC candidate. A supplied invalid/inactive key fails with 401 and a valid key without submission:create fails with 403; neither failure creates a candidate. Prefer propose_private_skill for anything repository-derived, organization-specific, or otherwise not intended for the public registry because that endpoint structurally requires organization auth. Use propose_skill_idea only when a public candidate is an acceptable outcome. Check `visibility` (organization_private or public_candidate) and `owner_scope` in the successful response, and report where the candidate landed. Never remove or bypass an organization key to force public submission: submit privately, then use the reviewed public-propagation flow. Send repository-derived private instructions only when Remembrance is an organization-approved destination. If host policy blocks the export, report that nothing was sent, do not retry, and do not create a handoff unless an organization admin explicitly requests one.",
|
|
9948
10883
|
"/api/v1/agent/skill-ideas",
|
|
9949
10884
|
skillIdeaRequestSchema
|
|
9950
10885
|
),
|
|
9951
10886
|
tool(
|
|
9952
10887
|
"propose_private_skill",
|
|
9953
|
-
"Submit a proposed skill only to the authenticated organization's private review queue. Privacy here is STRUCTURAL, not credential-dependent: this endpoint rejects anonymous use and never creates a public candidate, so unlike propose_skill_idea it cannot silently fall through to public when a key fails to resolve \u2014 it fails closed with 401/403 instead. Prefer it whenever the content is repository-derived, organization-specific, or must not reach the public registry; that preference is about removing the credential from the privacy decision, not about propose_skill_idea being unsafe when a key is present. Use it for repository-derived instructions only when Remembrance is an organization-approved destination. The response reports `visibility` (always organization_private here) \u2014 a rejection means nothing was submitted. If host policy blocks the export, report that nothing was sent, never retry through another transport, and do not create a handoff unless an organization admin explicitly requests one.",
|
|
10888
|
+
"Submit a complete proposed skill only to the authenticated organization's private review queue. This full skill lane retains bounded markdown, public citations, snippets, metadata, and actionable instructions through normal safety and review; do not compress a finished skill into the narrow private-lesson contract. Privacy here is STRUCTURAL, not credential-dependent: this endpoint rejects anonymous use and never creates a public candidate, so unlike propose_skill_idea it cannot silently fall through to public when a key fails to resolve \u2014 it fails closed with 401/403 instead. Prefer it whenever the content is repository-derived, organization-specific, or must not reach the public registry; that preference is about removing the credential from the privacy decision, not about propose_skill_idea being unsafe when a key is present. Use it for repository-derived instructions only when Remembrance is an organization-approved destination. The response reports `visibility` (always organization_private here) \u2014 a rejection means nothing was submitted. If host policy blocks the export, report that nothing was sent, never retry through another transport, and do not create a handoff unless an organization admin explicitly requests one.",
|
|
9954
10889
|
"/api/v1/agent/private-skill-ideas",
|
|
9955
10890
|
skillIdeaRequestSchema
|
|
9956
10891
|
),
|
|
@@ -10007,13 +10942,13 @@ function localTool(name, description, local, schema) {
|
|
|
10007
10942
|
};
|
|
10008
10943
|
}
|
|
10009
10944
|
function annotationsForTool(name) {
|
|
10010
|
-
const readOnly = name === "list_skills" || name === "get_effective_preferences" || name === "get_value_proof" || name === "run_connection_doctor";
|
|
10945
|
+
const readOnly = name === "list_skills" || name === "get_effective_preferences" || name === "get_value_proof" || name === "run_connection_doctor" || name === "get_private_lesson_policy" || name === "inspect_private_lesson_outbox";
|
|
10011
10946
|
const openWorld = name === "submit_feedback" || name === "submit_remembrance" || name === "propose_skill_idea" || name === "submit_suggestion" || name === "submit_resource" || name === "submit_resource_review";
|
|
10012
10947
|
return {
|
|
10013
10948
|
readOnlyHint: readOnly,
|
|
10014
10949
|
openWorldHint: openWorld,
|
|
10015
|
-
destructiveHint:
|
|
10016
|
-
...readOnly || name === "queue_private_skill_import" ? { idempotentHint: true } : {}
|
|
10950
|
+
destructiveHint: name === "delete_private_lesson_candidate",
|
|
10951
|
+
...readOnly || name === "queue_private_skill_import" || name === "submit_private_lesson_candidate" || name === "retry_private_lesson_candidate" ? { idempotentHint: true } : {}
|
|
10017
10952
|
};
|
|
10018
10953
|
}
|
|
10019
10954
|
function inputSchemaFor(schema, name) {
|
|
@@ -10022,7 +10957,29 @@ function inputSchemaFor(schema, name) {
|
|
|
10022
10957
|
$refStrategy: "none"
|
|
10023
10958
|
});
|
|
10024
10959
|
const definitions = converted.definitions;
|
|
10025
|
-
|
|
10960
|
+
const resolved = definitions?.[name] ?? converted;
|
|
10961
|
+
if (name === "submit_private_lesson_candidate") {
|
|
10962
|
+
const alternatives = Array.isArray(resolved.anyOf) ? resolved.anyOf : [];
|
|
10963
|
+
const properties = collectUnionProperties(resolved);
|
|
10964
|
+
return {
|
|
10965
|
+
type: "object",
|
|
10966
|
+
properties,
|
|
10967
|
+
additionalProperties: false,
|
|
10968
|
+
anyOf: alternatives
|
|
10969
|
+
};
|
|
10970
|
+
}
|
|
10971
|
+
return resolved.type === "object" ? resolved : { type: "object", ...resolved };
|
|
10972
|
+
}
|
|
10973
|
+
function collectUnionProperties(schema) {
|
|
10974
|
+
const own = schema.properties && typeof schema.properties === "object" ? schema.properties : {};
|
|
10975
|
+
const alternatives = Array.isArray(schema.anyOf) ? schema.anyOf : [];
|
|
10976
|
+
return Object.assign(
|
|
10977
|
+
{},
|
|
10978
|
+
own,
|
|
10979
|
+
...alternatives.map(
|
|
10980
|
+
(alternative) => alternative && typeof alternative === "object" ? collectUnionProperties(alternative) : {}
|
|
10981
|
+
)
|
|
10982
|
+
);
|
|
10026
10983
|
}
|
|
10027
10984
|
|
|
10028
10985
|
// src/private-skill-handoff.ts
|
|
@@ -10092,7 +11049,7 @@ import { homedir as homedir3 } from "node:os";
|
|
|
10092
11049
|
import { join as join3 } from "node:path";
|
|
10093
11050
|
|
|
10094
11051
|
// src/local-agent-identity.ts
|
|
10095
|
-
import { createPrivateKey, createPublicKey as
|
|
11052
|
+
import { createPrivateKey, createPublicKey as createPublicKey3 } from "node:crypto";
|
|
10096
11053
|
import { existsSync } from "node:fs";
|
|
10097
11054
|
import { homedir as homedir2 } from "node:os";
|
|
10098
11055
|
import { join as join2 } from "node:path";
|
|
@@ -10249,11 +11206,11 @@ function parseStoredIdentity(value) {
|
|
|
10249
11206
|
}
|
|
10250
11207
|
if (!Number.isFinite(Date.parse(value.created_at))) return null;
|
|
10251
11208
|
try {
|
|
10252
|
-
const publicDer =
|
|
11209
|
+
const publicDer = createPublicKey3(value.public_key).export({
|
|
10253
11210
|
type: "spki",
|
|
10254
11211
|
format: "der"
|
|
10255
11212
|
});
|
|
10256
|
-
const derivedPublicDer =
|
|
11213
|
+
const derivedPublicDer = createPublicKey3(
|
|
10257
11214
|
createPrivateKey(value.private_key)
|
|
10258
11215
|
).export({ type: "spki", format: "der" });
|
|
10259
11216
|
if (!publicDer.equals(derivedPublicDer)) return null;
|
|
@@ -10454,7 +11411,7 @@ function resolveApiAccess(env = process.env, fileSystem = nodeFileSystem2) {
|
|
|
10454
11411
|
return {
|
|
10455
11412
|
configuration,
|
|
10456
11413
|
credential,
|
|
10457
|
-
memberLinkToken: env.REMEMBRANCE_MEMBER_LINK_TOKEN?.trim()
|
|
11414
|
+
memberLinkToken: env.REMEMBRANCE_MEMBER_LINK_TOKEN?.trim() || shared.config.memberLinkToken || ""
|
|
10458
11415
|
};
|
|
10459
11416
|
}
|
|
10460
11417
|
function isUnusableCredentialSource(source) {
|
|
@@ -10702,8 +11659,9 @@ function localPluginLifecycleHealth(options) {
|
|
|
10702
11659
|
if (paths.length === 0) {
|
|
10703
11660
|
return missingPluginLifecycleHealth(surface);
|
|
10704
11661
|
}
|
|
10705
|
-
|
|
10706
|
-
|
|
11662
|
+
const nowMs = (options.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
11663
|
+
const genericPath = join3(healthDir, `${surface}.json`);
|
|
11664
|
+
const markerEntries = [];
|
|
10707
11665
|
for (const path of paths) {
|
|
10708
11666
|
try {
|
|
10709
11667
|
const parsed = JSON.parse(
|
|
@@ -10712,15 +11670,20 @@ function localPluginLifecycleHealth(options) {
|
|
|
10712
11670
|
if (!isRecord3(parsed) || parsed.surface !== surface) {
|
|
10713
11671
|
continue;
|
|
10714
11672
|
}
|
|
10715
|
-
const
|
|
10716
|
-
|
|
10717
|
-
|
|
10718
|
-
|
|
10719
|
-
|
|
11673
|
+
const components2 = isRecord3(parsed.components) ? parsed.components : {};
|
|
11674
|
+
markerEntries.push({
|
|
11675
|
+
path,
|
|
11676
|
+
marker: parsed,
|
|
11677
|
+
lastSeenMs: validLifecycleTimestamp(parsed.last_seen_at, nowMs),
|
|
11678
|
+
sessionStartMs: validLifecycleTimestamp(
|
|
11679
|
+
components2.session_start,
|
|
11680
|
+
nowMs
|
|
11681
|
+
)
|
|
11682
|
+
});
|
|
10720
11683
|
} catch {
|
|
10721
11684
|
}
|
|
10722
11685
|
}
|
|
10723
|
-
if (
|
|
11686
|
+
if (markerEntries.length === 0) {
|
|
10724
11687
|
return {
|
|
10725
11688
|
...missingPluginLifecycleHealth(surface),
|
|
10726
11689
|
issues: [
|
|
@@ -10731,8 +11694,42 @@ function localPluginLifecycleHealth(options) {
|
|
|
10731
11694
|
]
|
|
10732
11695
|
};
|
|
10733
11696
|
}
|
|
10734
|
-
const
|
|
10735
|
-
|
|
11697
|
+
const genericEntry = markerEntries.find(
|
|
11698
|
+
(entry) => entry.path === genericPath
|
|
11699
|
+
);
|
|
11700
|
+
const currentSessionStartMs = genericEntry?.sessionStartMs;
|
|
11701
|
+
const currentSessionEntries = typeof currentSessionStartMs === "number" ? markerEntries.filter(
|
|
11702
|
+
(entry) => entry.sessionStartMs >= currentSessionStartMs
|
|
11703
|
+
) : [];
|
|
11704
|
+
const cohort = currentSessionEntries.length > 0 ? currentSessionEntries : [
|
|
11705
|
+
markerEntries.reduce(
|
|
11706
|
+
(latest, candidate) => candidate.lastSeenMs > latest.lastSeenMs ? candidate : latest
|
|
11707
|
+
)
|
|
11708
|
+
];
|
|
11709
|
+
const newestEntry = cohort.reduce(
|
|
11710
|
+
(latest, candidate) => candidate.lastSeenMs > latest.lastSeenMs ? candidate : latest
|
|
11711
|
+
);
|
|
11712
|
+
const mergedComponents = {};
|
|
11713
|
+
for (const component of PLUGIN_HEALTH_COMPONENTS) {
|
|
11714
|
+
let latestValue;
|
|
11715
|
+
let latestMs = Number.NEGATIVE_INFINITY;
|
|
11716
|
+
for (const entry of cohort) {
|
|
11717
|
+
const components2 = isRecord3(entry.marker.components) ? entry.marker.components : {};
|
|
11718
|
+
const value = components2[component];
|
|
11719
|
+
const valueMs = validLifecycleTimestamp(value, nowMs);
|
|
11720
|
+
if (valueMs > latestMs) {
|
|
11721
|
+
latestMs = valueMs;
|
|
11722
|
+
latestValue = value;
|
|
11723
|
+
}
|
|
11724
|
+
}
|
|
11725
|
+
if (Number.isFinite(latestMs)) mergedComponents[component] = latestValue;
|
|
11726
|
+
}
|
|
11727
|
+
const marker = {
|
|
11728
|
+
...newestEntry.marker,
|
|
11729
|
+
components: mergedComponents,
|
|
11730
|
+
last_seen_at: Number.isFinite(newestEntry.lastSeenMs) ? new Date(newestEntry.lastSeenMs).toISOString() : newestEntry.marker.last_seen_at
|
|
11731
|
+
};
|
|
11732
|
+
const componentRecord = mergedComponents;
|
|
10736
11733
|
const sessionStartMs = validLifecycleTimestamp(
|
|
10737
11734
|
componentRecord.session_start,
|
|
10738
11735
|
nowMs
|
|
@@ -10840,7 +11837,7 @@ function localPluginLifecycleHealth(options) {
|
|
|
10840
11837
|
hook_trust: hookTrust,
|
|
10841
11838
|
components,
|
|
10842
11839
|
issues,
|
|
10843
|
-
explanation: "Each host session is tracked independently.
|
|
11840
|
+
explanation: "Each host process session is tracked independently. Turn-specific prompt, tool, and completion observations are combined only when they share the current SessionStart marker."
|
|
10844
11841
|
};
|
|
10845
11842
|
}
|
|
10846
11843
|
function normalizedPluginHookTrust(value) {
|
|
@@ -10918,6 +11915,7 @@ function emptyPluginComponents() {
|
|
|
10918
11915
|
}
|
|
10919
11916
|
function lifecycleMarkerPaths(surface, healthDir, fileSystem) {
|
|
10920
11917
|
const legacyPath = join3(healthDir, `${surface}.json`);
|
|
11918
|
+
const paths = fileSystem.exists(legacyPath) ? [legacyPath] : [];
|
|
10921
11919
|
const listed = fileSystem.list ? (() => {
|
|
10922
11920
|
try {
|
|
10923
11921
|
return fileSystem.list(healthDir);
|
|
@@ -10927,9 +11925,9 @@ function lifecycleMarkerPaths(surface, healthDir, fileSystem) {
|
|
|
10927
11925
|
})() : [];
|
|
10928
11926
|
const sessionPaths = listed.filter(
|
|
10929
11927
|
(name) => name.startsWith(`${surface}.`) && name.endsWith(".json") && /^[a-z_]+\.[a-f0-9]{24}\.json$/.test(name)
|
|
10930
|
-
).slice(0, MAX_LOCAL_LIFECYCLE_MARKERS).map((name) => join3(healthDir, name));
|
|
10931
|
-
|
|
10932
|
-
return [...new Set(
|
|
11928
|
+
).slice(0, MAX_LOCAL_LIFECYCLE_MARKERS - paths.length).map((name) => join3(healthDir, name));
|
|
11929
|
+
paths.push(...sessionPaths);
|
|
11930
|
+
return [...new Set(paths)].slice(0, MAX_LOCAL_LIFECYCLE_MARKERS);
|
|
10933
11931
|
}
|
|
10934
11932
|
function normalizeConfig(value) {
|
|
10935
11933
|
if (!isRecord3(value)) return {};
|
|
@@ -11080,10 +12078,1026 @@ async function checkClientUpdate(input) {
|
|
|
11080
12078
|
return status.status === "unavailable" ? null : status;
|
|
11081
12079
|
}
|
|
11082
12080
|
|
|
11083
|
-
// src/
|
|
12081
|
+
// src/private-lesson-outbox.ts
|
|
12082
|
+
import { createCipheriv, createDecipheriv, randomBytes as randomBytes2 } from "node:crypto";
|
|
12083
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
12084
|
+
import {
|
|
12085
|
+
chmod as chmod2,
|
|
12086
|
+
lstat,
|
|
12087
|
+
mkdir as mkdir2,
|
|
12088
|
+
open,
|
|
12089
|
+
readdir,
|
|
12090
|
+
rename,
|
|
12091
|
+
unlink
|
|
12092
|
+
} from "node:fs/promises";
|
|
12093
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
12094
|
+
import { homedir as homedir4 } from "node:os";
|
|
12095
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
12096
|
+
|
|
12097
|
+
// src/private-lesson-keychain.ts
|
|
12098
|
+
import { randomBytes } from "node:crypto";
|
|
12099
|
+
import { execFileSync } from "node:child_process";
|
|
12100
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
12101
|
+
var KEYCHAIN_SERVICE = "dev.remembrance.private-lessons";
|
|
12102
|
+
var defaultDependencies = {
|
|
12103
|
+
platform: process.platform,
|
|
12104
|
+
securityExists: existsSync3("/usr/bin/security"),
|
|
12105
|
+
execFile: execFileSync,
|
|
12106
|
+
randomKey: null,
|
|
12107
|
+
uid: typeof process.getuid === "function" ? process.getuid() : null
|
|
12108
|
+
};
|
|
12109
|
+
function readPrivateLessonKeychainKey(env, dependencies = defaultDependencies) {
|
|
12110
|
+
if (!keychainEnabled(env, dependencies)) return null;
|
|
12111
|
+
try {
|
|
12112
|
+
return decodeKey(
|
|
12113
|
+
String(
|
|
12114
|
+
dependencies.execFile(
|
|
12115
|
+
"/usr/bin/security",
|
|
12116
|
+
[
|
|
12117
|
+
"find-generic-password",
|
|
12118
|
+
"-s",
|
|
12119
|
+
KEYCHAIN_SERVICE,
|
|
12120
|
+
"-a",
|
|
12121
|
+
keychainAccount(dependencies),
|
|
12122
|
+
"-w"
|
|
12123
|
+
],
|
|
12124
|
+
{
|
|
12125
|
+
encoding: "utf8",
|
|
12126
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
12127
|
+
timeout: 2e3
|
|
12128
|
+
}
|
|
12129
|
+
)
|
|
12130
|
+
).trim()
|
|
12131
|
+
);
|
|
12132
|
+
} catch {
|
|
12133
|
+
return null;
|
|
12134
|
+
}
|
|
12135
|
+
}
|
|
12136
|
+
function resolvePrivateLessonKeychainKey(env, dependencies = defaultDependencies) {
|
|
12137
|
+
if (!keychainEnabled(env, dependencies)) return null;
|
|
12138
|
+
const existing = readPrivateLessonKeychainKey(env, dependencies);
|
|
12139
|
+
if (existing) return existing;
|
|
12140
|
+
const encoded = dependencies.randomKey ?? randomBytes(32).toString("base64url");
|
|
12141
|
+
const generated = decodeKey(encoded);
|
|
12142
|
+
if (!generated) return null;
|
|
12143
|
+
try {
|
|
12144
|
+
dependencies.execFile(
|
|
12145
|
+
"/usr/bin/security",
|
|
12146
|
+
[
|
|
12147
|
+
"add-generic-password",
|
|
12148
|
+
"-s",
|
|
12149
|
+
KEYCHAIN_SERVICE,
|
|
12150
|
+
"-a",
|
|
12151
|
+
keychainAccount(dependencies),
|
|
12152
|
+
"-w",
|
|
12153
|
+
encoded
|
|
12154
|
+
],
|
|
12155
|
+
{ stdio: "ignore", timeout: 2e3 }
|
|
12156
|
+
);
|
|
12157
|
+
return generated;
|
|
12158
|
+
} catch {
|
|
12159
|
+
return readPrivateLessonKeychainKey(env, dependencies);
|
|
12160
|
+
}
|
|
12161
|
+
}
|
|
12162
|
+
function keychainEnabled(env, dependencies) {
|
|
12163
|
+
return !(env.NODE_ENV === "test" || /^(0|false|no)$/i.test(env.REMEMBRANCE_PRIVATE_LESSON_KEYCHAIN ?? "") || dependencies.platform !== "darwin" || !dependencies.securityExists);
|
|
12164
|
+
}
|
|
12165
|
+
function keychainAccount(dependencies) {
|
|
12166
|
+
return `uid-${dependencies.uid ?? "user"}`;
|
|
12167
|
+
}
|
|
12168
|
+
function decodeKey(encoded) {
|
|
12169
|
+
const key = Buffer.from(encoded, "base64url");
|
|
12170
|
+
return key.length === 32 ? key : null;
|
|
12171
|
+
}
|
|
12172
|
+
|
|
12173
|
+
// src/private-lesson-outbox.ts
|
|
12174
|
+
var PRIVATE_LESSON_OUTBOX_VERSION = "private-lesson-outbox-v1";
|
|
12175
|
+
var PRIVATE_LESSON_OUTBOX_MAX_BYTES = 64 * 1024 * 1024;
|
|
12176
|
+
var PRIVATE_LESSON_OUTBOX_MIN_BYTES = 1024 * 1024;
|
|
12177
|
+
var PRIVATE_LESSON_OUTBOX_MAX_CONFIGURED_BYTES = 1024 * 1024 * 1024;
|
|
12178
|
+
var PRIVATE_LESSON_DRAFT_MAX_BYTES = 64 * 1024;
|
|
12179
|
+
var PRIVATE_LESSON_OUTBOX_LOCK_STALE_MS = 3e4;
|
|
12180
|
+
var PRIVATE_LESSON_OUTBOX_LOCK_WAIT_MS = 5e3;
|
|
12181
|
+
var privateLessonOutboxLockContext = new AsyncLocalStorage();
|
|
12182
|
+
var PrivateLessonPolicyFloorError = class extends Error {
|
|
12183
|
+
constructor(message, code) {
|
|
12184
|
+
super(message);
|
|
12185
|
+
this.code = code;
|
|
12186
|
+
this.name = "PrivateLessonPolicyFloorError";
|
|
12187
|
+
}
|
|
12188
|
+
code;
|
|
12189
|
+
};
|
|
12190
|
+
async function queuePreparedPrivateLesson(prepared, policy, options = {}) {
|
|
12191
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
12192
|
+
const outbox = await readyOutbox(options);
|
|
12193
|
+
const summary = await privateLessonOutboxSummary({
|
|
12194
|
+
...options,
|
|
12195
|
+
outboxDirectory: outbox.directory,
|
|
12196
|
+
encryption: outbox.encryption
|
|
12197
|
+
});
|
|
12198
|
+
if (summary.paused_at_ceiling) {
|
|
12199
|
+
throw new Error(
|
|
12200
|
+
"Private lesson capture is paused because the local 64 MiB outbox safety ceiling was reached."
|
|
12201
|
+
);
|
|
12202
|
+
}
|
|
12203
|
+
const draftId = createPublicId("pld");
|
|
12204
|
+
const createdAt = now.toISOString();
|
|
12205
|
+
const state = prepared.status === "held_safety" ? "held_safety" : policy ? "ready" : "awaiting_authorization";
|
|
12206
|
+
const draft = {
|
|
12207
|
+
version: PRIVATE_LESSON_OUTBOX_VERSION,
|
|
12208
|
+
draft_id: draftId,
|
|
12209
|
+
state,
|
|
12210
|
+
candidate: prepared.candidate,
|
|
12211
|
+
canonical_payload: prepared.canonical_payload,
|
|
12212
|
+
payload_digest: prepared.payload_digest,
|
|
12213
|
+
redaction_version: prepared.candidate.redaction_version,
|
|
12214
|
+
redactor_digest: prepared.redactor_digest,
|
|
12215
|
+
held_reasons: prepared.held_reasons,
|
|
12216
|
+
policy,
|
|
12217
|
+
idempotency_key: `private-lesson-${hashValue(
|
|
12218
|
+
canonicalJson({
|
|
12219
|
+
draft_id: draftId,
|
|
12220
|
+
payload_digest: prepared.payload_digest
|
|
12221
|
+
})
|
|
12222
|
+
).replace(/^sha256:/, "")}`,
|
|
12223
|
+
attempts: 0,
|
|
12224
|
+
next_retry_at: null,
|
|
12225
|
+
last_error_code: null,
|
|
12226
|
+
created_at: createdAt,
|
|
12227
|
+
updated_at: createdAt,
|
|
12228
|
+
submitted_at: null,
|
|
12229
|
+
receipt_digest: null,
|
|
12230
|
+
terminal_reason: null,
|
|
12231
|
+
hold_telemetry_status: prepared.status === "held_safety" ? "pending" : "not_required",
|
|
12232
|
+
hold_event_digest: null,
|
|
12233
|
+
hold_receipt_digest: null,
|
|
12234
|
+
hold_reported_at: null
|
|
12235
|
+
};
|
|
12236
|
+
await writeDraft(draft, outbox, false, summary.total_bytes);
|
|
12237
|
+
return draft;
|
|
12238
|
+
}
|
|
12239
|
+
async function readPrivateLessonDraft(draftId, options = {}) {
|
|
12240
|
+
assertDraftId(draftId);
|
|
12241
|
+
const outbox = await readyOutbox(options);
|
|
12242
|
+
return withPrivateLessonOutboxLock(outbox.directory, async () => {
|
|
12243
|
+
if (existsSync4(tombstonePath(outbox.directory, draftId))) {
|
|
12244
|
+
throw new Error("Private lesson draft was explicitly deleted.");
|
|
12245
|
+
}
|
|
12246
|
+
const path = draftPath(outbox.directory, draftId);
|
|
12247
|
+
await assertRegularPrivateFile(path, PRIVATE_LESSON_DRAFT_MAX_BYTES);
|
|
12248
|
+
const serialized = await readBoundedFile(
|
|
12249
|
+
path,
|
|
12250
|
+
PRIVATE_LESSON_DRAFT_MAX_BYTES
|
|
12251
|
+
);
|
|
12252
|
+
const envelope = parseEnvelope(serialized, draftId);
|
|
12253
|
+
const draft = decryptDraft(envelope, outbox.encryption, true);
|
|
12254
|
+
const reconciled = terminalizeUnsupportedProfile(
|
|
12255
|
+
draft,
|
|
12256
|
+
options.now ?? /* @__PURE__ */ new Date()
|
|
12257
|
+
);
|
|
12258
|
+
if (reconciled !== draft) {
|
|
12259
|
+
await writeDraft(reconciled, outbox, true, void 0, true);
|
|
12260
|
+
}
|
|
12261
|
+
return reconciled;
|
|
12262
|
+
});
|
|
12263
|
+
}
|
|
12264
|
+
async function reconcilePrivateLessonOutboxProfiles(options = {}) {
|
|
12265
|
+
const outbox = await readyOutbox(options);
|
|
12266
|
+
return withPrivateLessonOutboxLock(outbox.directory, async () => {
|
|
12267
|
+
await reconcileExplicitDeletionTombstones(outbox.directory);
|
|
12268
|
+
const entries = await secureOutboxEntries(outbox.directory);
|
|
12269
|
+
let inspected = 0;
|
|
12270
|
+
let terminalized = 0;
|
|
12271
|
+
for (const name of entries.filter(
|
|
12272
|
+
(entry) => entry.endsWith(".lesson.json")
|
|
12273
|
+
)) {
|
|
12274
|
+
const draftId = name.slice(0, -".lesson.json".length);
|
|
12275
|
+
const before = parseEnvelope(
|
|
12276
|
+
await readBoundedFile(
|
|
12277
|
+
draftPath(outbox.directory, draftId),
|
|
12278
|
+
PRIVATE_LESSON_DRAFT_MAX_BYTES
|
|
12279
|
+
),
|
|
12280
|
+
draftId
|
|
12281
|
+
).state;
|
|
12282
|
+
const draft = await readPrivateLessonDraft(draftId, {
|
|
12283
|
+
...options,
|
|
12284
|
+
outboxDirectory: outbox.directory,
|
|
12285
|
+
encryption: outbox.encryption
|
|
12286
|
+
});
|
|
12287
|
+
inspected += 1;
|
|
12288
|
+
if (before !== "superseded_redactor" && draft.state === "superseded_redactor") {
|
|
12289
|
+
terminalized += 1;
|
|
12290
|
+
}
|
|
12291
|
+
}
|
|
12292
|
+
return { inspected, terminalized };
|
|
12293
|
+
});
|
|
12294
|
+
}
|
|
12295
|
+
async function listPrivateLessonDrafts(options = {}) {
|
|
12296
|
+
const outbox = await readyOutbox(options);
|
|
12297
|
+
await reconcilePrivateLessonOutboxProfiles({
|
|
12298
|
+
...options,
|
|
12299
|
+
outboxDirectory: outbox.directory,
|
|
12300
|
+
encryption: outbox.encryption
|
|
12301
|
+
});
|
|
12302
|
+
const entries = await secureOutboxEntries(outbox.directory);
|
|
12303
|
+
const limit = Math.max(1, Math.min(options.limit ?? 50, 200));
|
|
12304
|
+
const drafts = [];
|
|
12305
|
+
for (const name of entries.filter((item) => item.endsWith(".lesson.json"))) {
|
|
12306
|
+
if (drafts.length >= limit) break;
|
|
12307
|
+
const draft = await readPrivateLessonDraft(
|
|
12308
|
+
name.slice(0, -".lesson.json".length),
|
|
12309
|
+
{
|
|
12310
|
+
...options,
|
|
12311
|
+
outboxDirectory: outbox.directory,
|
|
12312
|
+
encryption: outbox.encryption
|
|
12313
|
+
}
|
|
12314
|
+
);
|
|
12315
|
+
drafts.push({
|
|
12316
|
+
draft_id: draft.draft_id,
|
|
12317
|
+
state: draft.state,
|
|
12318
|
+
payload_digest: draft.payload_digest,
|
|
12319
|
+
attempts: draft.attempts,
|
|
12320
|
+
next_retry_at: draft.next_retry_at,
|
|
12321
|
+
last_error_code: draft.last_error_code,
|
|
12322
|
+
created_at: draft.created_at,
|
|
12323
|
+
updated_at: draft.updated_at,
|
|
12324
|
+
submitted_at: draft.submitted_at,
|
|
12325
|
+
terminal_reason: draft.terminal_reason,
|
|
12326
|
+
hold_telemetry_status: draft.hold_telemetry_status,
|
|
12327
|
+
hold_event_digest: draft.hold_event_digest,
|
|
12328
|
+
hold_reported_at: draft.hold_reported_at
|
|
12329
|
+
});
|
|
12330
|
+
}
|
|
12331
|
+
return drafts.sort(
|
|
12332
|
+
(left, right) => right.updated_at.localeCompare(left.updated_at)
|
|
12333
|
+
);
|
|
12334
|
+
}
|
|
12335
|
+
async function updatePrivateLessonDraft(draftId, update, options = {}) {
|
|
12336
|
+
const outbox = await readyOutbox(options);
|
|
12337
|
+
return withPrivateLessonOutboxLock(outbox.directory, async () => {
|
|
12338
|
+
const current = await readPrivateLessonDraft(draftId, {
|
|
12339
|
+
...options,
|
|
12340
|
+
outboxDirectory: outbox.directory,
|
|
12341
|
+
encryption: outbox.encryption
|
|
12342
|
+
});
|
|
12343
|
+
const next = validateDraft(update(structuredClone(current)));
|
|
12344
|
+
if (next.draft_id !== current.draft_id || next.payload_digest !== current.payload_digest || next.canonical_payload !== current.canonical_payload || next.redaction_version !== current.redaction_version || next.redactor_digest !== current.redactor_digest || next.idempotency_key !== current.idempotency_key) {
|
|
12345
|
+
throw new Error("Private lesson draft content is immutable.");
|
|
12346
|
+
}
|
|
12347
|
+
next.updated_at = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
12348
|
+
await writeDraft(next, outbox, true);
|
|
12349
|
+
return next;
|
|
12350
|
+
});
|
|
12351
|
+
}
|
|
12352
|
+
async function deletePrivateLessonDraft(draftId, confirmed, options = {}) {
|
|
12353
|
+
if (!confirmed) {
|
|
12354
|
+
throw new Error(
|
|
12355
|
+
"Explicit confirmation is required to delete a private lesson draft."
|
|
12356
|
+
);
|
|
12357
|
+
}
|
|
12358
|
+
assertDraftId(draftId);
|
|
12359
|
+
const outbox = await readyOutbox(options);
|
|
12360
|
+
return withPrivateLessonOutboxLock(outbox.directory, async () => {
|
|
12361
|
+
const path = draftPath(outbox.directory, draftId);
|
|
12362
|
+
await assertRegularPrivateFile(path, PRIVATE_LESSON_DRAFT_MAX_BYTES);
|
|
12363
|
+
const tombstone = {
|
|
12364
|
+
version: "private-lesson-outbox-deletion-v1",
|
|
12365
|
+
draft_id: draftId,
|
|
12366
|
+
deleted_at: (options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
12367
|
+
reason: "explicit_confirmed_deletion"
|
|
12368
|
+
};
|
|
12369
|
+
await atomicWritePrivate(
|
|
12370
|
+
tombstonePath(outbox.directory, draftId),
|
|
12371
|
+
`${JSON.stringify(tombstone)}
|
|
12372
|
+
`
|
|
12373
|
+
);
|
|
12374
|
+
await unlink(path);
|
|
12375
|
+
await syncDirectory(outbox.directory);
|
|
12376
|
+
return tombstone;
|
|
12377
|
+
});
|
|
12378
|
+
}
|
|
12379
|
+
async function privateLessonOutboxSummary(options = {}) {
|
|
12380
|
+
const outbox = await readyOutbox(options);
|
|
12381
|
+
await reconcilePrivateLessonOutboxProfiles({
|
|
12382
|
+
...options,
|
|
12383
|
+
outboxDirectory: outbox.directory,
|
|
12384
|
+
encryption: outbox.encryption
|
|
12385
|
+
});
|
|
12386
|
+
const entries = await secureOutboxEntries(outbox.directory);
|
|
12387
|
+
const counts = {
|
|
12388
|
+
ready: 0,
|
|
12389
|
+
awaiting_authorization: 0,
|
|
12390
|
+
retry_scheduled: 0,
|
|
12391
|
+
held_safety: 0,
|
|
12392
|
+
submitted: 0,
|
|
12393
|
+
superseded_redactor: 0
|
|
12394
|
+
};
|
|
12395
|
+
let totalBytes = 0;
|
|
12396
|
+
let deletionTombstones = 0;
|
|
12397
|
+
let terminalBytes = 0;
|
|
12398
|
+
const terminalReasons = {};
|
|
12399
|
+
for (const name of entries) {
|
|
12400
|
+
const path = join4(outbox.directory, name);
|
|
12401
|
+
const metadata = await lstat(path);
|
|
12402
|
+
totalBytes += metadata.size;
|
|
12403
|
+
if (name.endsWith(".deleted.json")) {
|
|
12404
|
+
deletionTombstones += 1;
|
|
12405
|
+
continue;
|
|
12406
|
+
}
|
|
12407
|
+
if (!name.endsWith(".lesson.json")) continue;
|
|
12408
|
+
const envelope = parseEnvelope(
|
|
12409
|
+
await readBoundedFile(path, PRIVATE_LESSON_DRAFT_MAX_BYTES),
|
|
12410
|
+
name.slice(0, -".lesson.json".length)
|
|
12411
|
+
);
|
|
12412
|
+
counts[envelope.state] += 1;
|
|
12413
|
+
if (envelope.state === "superseded_redactor") {
|
|
12414
|
+
terminalBytes += metadata.size;
|
|
12415
|
+
const draft = await readPrivateLessonDraft(
|
|
12416
|
+
name.slice(0, -".lesson.json".length),
|
|
12417
|
+
{
|
|
12418
|
+
...options,
|
|
12419
|
+
outboxDirectory: outbox.directory,
|
|
12420
|
+
encryption: outbox.encryption
|
|
12421
|
+
}
|
|
12422
|
+
);
|
|
12423
|
+
const reason = draft.terminal_reason;
|
|
12424
|
+
terminalReasons[reason] = (terminalReasons[reason] ?? 0) + 1;
|
|
12425
|
+
}
|
|
12426
|
+
}
|
|
12427
|
+
return {
|
|
12428
|
+
directory: outbox.directory,
|
|
12429
|
+
encryption_source: outbox.encryption.source,
|
|
12430
|
+
total_bytes: totalBytes,
|
|
12431
|
+
ceiling_bytes: outbox.ceilingBytes,
|
|
12432
|
+
paused_at_ceiling: totalBytes >= outbox.ceilingBytes,
|
|
12433
|
+
counts,
|
|
12434
|
+
terminal_count: counts.superseded_redactor,
|
|
12435
|
+
terminal_bytes: terminalBytes,
|
|
12436
|
+
terminal_reasons: terminalReasons,
|
|
12437
|
+
deletion_guidance: counts.superseded_redactor > 0 ? "Inspect the retained draft, then use delete_private_lesson_candidate with confirm=true only when you explicitly choose to remove it." : null,
|
|
12438
|
+
deletion_tombstones: deletionTombstones
|
|
12439
|
+
};
|
|
12440
|
+
}
|
|
12441
|
+
async function inspectPrivateLessonOutboxHealth(options = {}) {
|
|
12442
|
+
const env = options.env ?? process.env;
|
|
12443
|
+
const directory = options.outboxDirectory ?? privateLessonOutboxDirectory(env, options.homeDirectory);
|
|
12444
|
+
const encryption = options.encryption ?? readExistingOutboxEncryption(env, options);
|
|
12445
|
+
if (!existsSync4(directory)) {
|
|
12446
|
+
return {
|
|
12447
|
+
storage_initialized: false,
|
|
12448
|
+
encryption_ready: encryption !== null,
|
|
12449
|
+
encryption_source: encryption?.source ?? "uninitialized",
|
|
12450
|
+
total_bytes: 0,
|
|
12451
|
+
ceiling_bytes: privateLessonOutboxMaxBytes(env),
|
|
12452
|
+
paused_at_ceiling: false,
|
|
12453
|
+
counts: emptyOutboxCounts(),
|
|
12454
|
+
terminal_count: 0,
|
|
12455
|
+
terminal_bytes: 0,
|
|
12456
|
+
terminal_reasons: {},
|
|
12457
|
+
deletion_guidance: null,
|
|
12458
|
+
deletion_tombstones: 0
|
|
12459
|
+
};
|
|
12460
|
+
}
|
|
12461
|
+
const metadata = await lstat(directory);
|
|
12462
|
+
assertPrivateLessonPathMetadata(metadata, "directory");
|
|
12463
|
+
if (encryption) {
|
|
12464
|
+
await reconcilePrivateLessonOutboxProfiles({
|
|
12465
|
+
...options,
|
|
12466
|
+
outboxDirectory: directory,
|
|
12467
|
+
encryption
|
|
12468
|
+
});
|
|
12469
|
+
}
|
|
12470
|
+
const entries = await secureOutboxEntries(directory);
|
|
12471
|
+
const counts = emptyOutboxCounts();
|
|
12472
|
+
let totalBytes = 0;
|
|
12473
|
+
let deletionTombstones = 0;
|
|
12474
|
+
let terminalBytes = 0;
|
|
12475
|
+
const terminalReasons = {};
|
|
12476
|
+
for (const name of entries) {
|
|
12477
|
+
const path = join4(directory, name);
|
|
12478
|
+
const entry = await lstat(path);
|
|
12479
|
+
totalBytes += entry.size;
|
|
12480
|
+
if (name.endsWith(".deleted.json")) {
|
|
12481
|
+
deletionTombstones += 1;
|
|
12482
|
+
continue;
|
|
12483
|
+
}
|
|
12484
|
+
if (!name.endsWith(".lesson.json")) continue;
|
|
12485
|
+
const envelope = parseEnvelope(
|
|
12486
|
+
await readBoundedFile(path, PRIVATE_LESSON_DRAFT_MAX_BYTES),
|
|
12487
|
+
name.slice(0, -".lesson.json".length)
|
|
12488
|
+
);
|
|
12489
|
+
counts[envelope.state] += 1;
|
|
12490
|
+
if (envelope.state === "superseded_redactor") {
|
|
12491
|
+
terminalBytes += entry.size;
|
|
12492
|
+
if (encryption) {
|
|
12493
|
+
const draft = await readPrivateLessonDraft(
|
|
12494
|
+
name.slice(0, -".lesson.json".length),
|
|
12495
|
+
{
|
|
12496
|
+
...options,
|
|
12497
|
+
outboxDirectory: directory,
|
|
12498
|
+
encryption
|
|
12499
|
+
}
|
|
12500
|
+
);
|
|
12501
|
+
const reason = draft.terminal_reason;
|
|
12502
|
+
terminalReasons[reason] = (terminalReasons[reason] ?? 0) + 1;
|
|
12503
|
+
}
|
|
12504
|
+
}
|
|
12505
|
+
}
|
|
12506
|
+
return {
|
|
12507
|
+
storage_initialized: true,
|
|
12508
|
+
encryption_ready: encryption !== null,
|
|
12509
|
+
encryption_source: encryption?.source ?? "uninitialized",
|
|
12510
|
+
total_bytes: totalBytes,
|
|
12511
|
+
ceiling_bytes: privateLessonOutboxMaxBytes(env),
|
|
12512
|
+
paused_at_ceiling: totalBytes >= privateLessonOutboxMaxBytes(env),
|
|
12513
|
+
counts,
|
|
12514
|
+
terminal_count: counts.superseded_redactor,
|
|
12515
|
+
terminal_bytes: terminalBytes,
|
|
12516
|
+
terminal_reasons: terminalReasons,
|
|
12517
|
+
deletion_guidance: counts.superseded_redactor > 0 ? "Inspect the retained draft, then use delete_private_lesson_candidate with confirm=true only when you explicitly choose to remove it." : null,
|
|
12518
|
+
deletion_tombstones: deletionTombstones
|
|
12519
|
+
};
|
|
12520
|
+
}
|
|
12521
|
+
function privateLessonOutboxDirectory(env = process.env, homeDirectory = homedir4()) {
|
|
12522
|
+
const stateRoot = env.XDG_STATE_HOME?.trim() || join4(homeDirectory, ".local", "state");
|
|
12523
|
+
return join4(stateRoot, "remembrance", "private-lessons");
|
|
12524
|
+
}
|
|
12525
|
+
function privateLessonOutboxKeyPath(env = process.env, homeDirectory = homedir4()) {
|
|
12526
|
+
const configRoot = env.XDG_CONFIG_HOME?.trim() || join4(homeDirectory, ".config");
|
|
12527
|
+
return join4(configRoot, "remembrance", "private-lesson-outbox.key");
|
|
12528
|
+
}
|
|
12529
|
+
function privateLessonOutboxMaxBytes(env = process.env) {
|
|
12530
|
+
const configured = env.REMEMBRANCE_PRIVATE_LESSON_OUTBOX_MAX_BYTES?.trim();
|
|
12531
|
+
if (!configured) return PRIVATE_LESSON_OUTBOX_MAX_BYTES;
|
|
12532
|
+
const value = Number(configured);
|
|
12533
|
+
if (!Number.isSafeInteger(value) || value < PRIVATE_LESSON_OUTBOX_MIN_BYTES || value > PRIVATE_LESSON_OUTBOX_MAX_CONFIGURED_BYTES) {
|
|
12534
|
+
throw new Error(
|
|
12535
|
+
`REMEMBRANCE_PRIVATE_LESSON_OUTBOX_MAX_BYTES must be an integer from ${PRIVATE_LESSON_OUTBOX_MIN_BYTES} to ${PRIVATE_LESSON_OUTBOX_MAX_CONFIGURED_BYTES}.`
|
|
12536
|
+
);
|
|
12537
|
+
}
|
|
12538
|
+
return value;
|
|
12539
|
+
}
|
|
12540
|
+
async function resolvePrivateLessonOutboxEncryption(options = {}) {
|
|
12541
|
+
if (options.encryption) return options.encryption;
|
|
12542
|
+
const keychain = resolvePrivateLessonKeychainKey(options.env ?? process.env);
|
|
12543
|
+
if (keychain) return { key: keychain, source: "os_keychain" };
|
|
12544
|
+
const path = privateLessonOutboxKeyPath(options.env, options.homeDirectory);
|
|
12545
|
+
await ensurePrivateLessonDirectory(dirname2(path));
|
|
12546
|
+
let encoded;
|
|
12547
|
+
try {
|
|
12548
|
+
encoded = await readConcurrentPrivateLessonFallbackKey(path);
|
|
12549
|
+
} catch (error) {
|
|
12550
|
+
if (!isMissingError(error)) throw error;
|
|
12551
|
+
encoded = randomBytes2(32).toString("base64url");
|
|
12552
|
+
try {
|
|
12553
|
+
await atomicWritePrivate(path, `${encoded}
|
|
12554
|
+
`);
|
|
12555
|
+
} catch (writeError) {
|
|
12556
|
+
if (!isAlreadyExistsError2(writeError)) throw writeError;
|
|
12557
|
+
encoded = await readConcurrentPrivateLessonFallbackKey(path);
|
|
12558
|
+
}
|
|
12559
|
+
}
|
|
12560
|
+
return { key: Buffer.from(encoded, "base64url"), source: "mode_0600_file" };
|
|
12561
|
+
}
|
|
12562
|
+
async function rememberPrivateLessonPolicyFloor(binding, options = {}) {
|
|
12563
|
+
const audience = digestHex(
|
|
12564
|
+
binding.organization_audience_hash,
|
|
12565
|
+
"organization audience"
|
|
12566
|
+
);
|
|
12567
|
+
const digest = digestHex(binding.policy_digest, "policy");
|
|
12568
|
+
if (!Number.isSafeInteger(binding.policy_sequence) || binding.policy_sequence < 1) {
|
|
12569
|
+
throw new PrivateLessonPolicyFloorError(
|
|
12570
|
+
"Private lesson policy sequence is invalid.",
|
|
12571
|
+
"policy_floor_invalid"
|
|
12572
|
+
);
|
|
12573
|
+
}
|
|
12574
|
+
const directory = options.outboxDirectory ?? privateLessonOutboxDirectory(options.env, options.homeDirectory);
|
|
12575
|
+
await ensurePrivateLessonDirectory(directory);
|
|
12576
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
12577
|
+
assertSecurePrivateLessonOutboxEntries(entries);
|
|
12578
|
+
const observed = entries.flatMap((entry) => {
|
|
12579
|
+
const match = entry.name.match(
|
|
12580
|
+
/^\.policy\.([a-f0-9]{64})\.(\d{1,16})\.([a-f0-9]{64})\.json$/
|
|
12581
|
+
);
|
|
12582
|
+
if (!match || match[1] !== audience) return [];
|
|
12583
|
+
return [{ sequence: Number(match[2]), digest: match[3] }];
|
|
12584
|
+
});
|
|
12585
|
+
const highest = observed.reduce(
|
|
12586
|
+
(maximum, item) => Math.max(maximum, item.sequence),
|
|
12587
|
+
0
|
|
12588
|
+
);
|
|
12589
|
+
if (highest > binding.policy_sequence) {
|
|
12590
|
+
throw new PrivateLessonPolicyFloorError(
|
|
12591
|
+
"Private lesson policy sequence moved backward.",
|
|
12592
|
+
"policy_downgrade"
|
|
12593
|
+
);
|
|
12594
|
+
}
|
|
12595
|
+
if (observed.some(
|
|
12596
|
+
(item) => item.sequence === binding.policy_sequence && item.digest !== digest
|
|
12597
|
+
)) {
|
|
12598
|
+
throw new PrivateLessonPolicyFloorError(
|
|
12599
|
+
"Private lesson policy conflicts at the same sequence.",
|
|
12600
|
+
"policy_sequence_conflict"
|
|
12601
|
+
);
|
|
12602
|
+
}
|
|
12603
|
+
const path = join4(
|
|
12604
|
+
directory,
|
|
12605
|
+
`.policy.${audience}.${binding.policy_sequence}.${digest}.json`
|
|
12606
|
+
);
|
|
12607
|
+
try {
|
|
12608
|
+
await atomicWritePrivate(
|
|
12609
|
+
path,
|
|
12610
|
+
`${JSON.stringify({
|
|
12611
|
+
version: "private-lesson-policy-floor-v1",
|
|
12612
|
+
organization_audience_hash: binding.organization_audience_hash,
|
|
12613
|
+
policy_sequence: binding.policy_sequence,
|
|
12614
|
+
policy_digest: binding.policy_digest
|
|
12615
|
+
})}
|
|
12616
|
+
`
|
|
12617
|
+
);
|
|
12618
|
+
} catch (error) {
|
|
12619
|
+
if (!isAlreadyExistsError2(error)) throw error;
|
|
12620
|
+
}
|
|
12621
|
+
}
|
|
12622
|
+
async function readyOutbox(options) {
|
|
12623
|
+
const directory = options.outboxDirectory ?? privateLessonOutboxDirectory(options.env, options.homeDirectory);
|
|
12624
|
+
await ensurePrivateLessonDirectory(directory);
|
|
12625
|
+
return {
|
|
12626
|
+
directory,
|
|
12627
|
+
encryption: await resolvePrivateLessonOutboxEncryption(options),
|
|
12628
|
+
ceilingBytes: privateLessonOutboxMaxBytes(options.env)
|
|
12629
|
+
};
|
|
12630
|
+
}
|
|
12631
|
+
async function ensurePrivateLessonDirectory(path) {
|
|
12632
|
+
await mkdir2(path, { recursive: true, mode: 448 });
|
|
12633
|
+
assertPrivateLessonPathMetadata(await lstat(path), "directory");
|
|
12634
|
+
await chmod2(path, 448);
|
|
12635
|
+
assertPrivateLessonPathMetadata(await lstat(path), "directory");
|
|
12636
|
+
}
|
|
12637
|
+
async function withPrivateLessonOutboxLock(directory, operation) {
|
|
12638
|
+
const active = privateLessonOutboxLockContext.getStore();
|
|
12639
|
+
if (active?.has(directory)) return operation();
|
|
12640
|
+
const lockPath = join4(directory, ".outbox.lock");
|
|
12641
|
+
const deadline = Date.now() + PRIVATE_LESSON_OUTBOX_LOCK_WAIT_MS;
|
|
12642
|
+
let handle = null;
|
|
12643
|
+
while (!handle) {
|
|
12644
|
+
let candidateHandle = null;
|
|
12645
|
+
try {
|
|
12646
|
+
candidateHandle = await open(lockPath, "wx", 384);
|
|
12647
|
+
await candidateHandle.writeFile(
|
|
12648
|
+
`${JSON.stringify({
|
|
12649
|
+
version: "private-lesson-outbox-lock-v1",
|
|
12650
|
+
pid: process.pid,
|
|
12651
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
12652
|
+
})}
|
|
12653
|
+
`,
|
|
12654
|
+
"utf8"
|
|
12655
|
+
);
|
|
12656
|
+
await candidateHandle.sync();
|
|
12657
|
+
await candidateHandle.chmod(384);
|
|
12658
|
+
await syncDirectory(directory);
|
|
12659
|
+
handle = candidateHandle;
|
|
12660
|
+
} catch (error) {
|
|
12661
|
+
if (!isAlreadyExistsError2(error)) {
|
|
12662
|
+
await candidateHandle?.close().catch(() => {
|
|
12663
|
+
});
|
|
12664
|
+
if (candidateHandle) {
|
|
12665
|
+
await unlink(lockPath).catch(() => {
|
|
12666
|
+
});
|
|
12667
|
+
await syncDirectory(directory);
|
|
12668
|
+
}
|
|
12669
|
+
throw error;
|
|
12670
|
+
}
|
|
12671
|
+
let metadata;
|
|
12672
|
+
try {
|
|
12673
|
+
metadata = await lstat(lockPath);
|
|
12674
|
+
} catch (statError) {
|
|
12675
|
+
if (isMissingError(statError)) metadata = null;
|
|
12676
|
+
else throw statError;
|
|
12677
|
+
}
|
|
12678
|
+
if (!metadata) continue;
|
|
12679
|
+
assertPrivateLessonPathMetadata(metadata, "file");
|
|
12680
|
+
if (Date.now() - metadata.mtimeMs > PRIVATE_LESSON_OUTBOX_LOCK_STALE_MS) {
|
|
12681
|
+
const stalePath = join4(
|
|
12682
|
+
directory,
|
|
12683
|
+
`.outbox.lock.stale-${randomBytes2(12).toString("hex")}`
|
|
12684
|
+
);
|
|
12685
|
+
try {
|
|
12686
|
+
await rename(lockPath, stalePath);
|
|
12687
|
+
await unlink(stalePath);
|
|
12688
|
+
await syncDirectory(directory);
|
|
12689
|
+
continue;
|
|
12690
|
+
} catch (reclaimError) {
|
|
12691
|
+
if (!isMissingError(reclaimError)) throw reclaimError;
|
|
12692
|
+
}
|
|
12693
|
+
}
|
|
12694
|
+
if (Date.now() >= deadline) {
|
|
12695
|
+
throw new Error("Private lesson outbox is busy; retry shortly.");
|
|
12696
|
+
}
|
|
12697
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
12698
|
+
}
|
|
12699
|
+
}
|
|
12700
|
+
const nextActive = new Set(active ?? []);
|
|
12701
|
+
nextActive.add(directory);
|
|
12702
|
+
try {
|
|
12703
|
+
return await privateLessonOutboxLockContext.run(nextActive, operation);
|
|
12704
|
+
} finally {
|
|
12705
|
+
const heldMetadata = await handle.stat().catch(() => null);
|
|
12706
|
+
await handle.close().catch(() => {
|
|
12707
|
+
});
|
|
12708
|
+
const currentMetadata = await lstat(lockPath).catch(() => null);
|
|
12709
|
+
if (heldMetadata && currentMetadata && heldMetadata.dev === currentMetadata.dev && heldMetadata.ino === currentMetadata.ino) {
|
|
12710
|
+
await unlink(lockPath).catch(() => {
|
|
12711
|
+
});
|
|
12712
|
+
await syncDirectory(directory);
|
|
12713
|
+
}
|
|
12714
|
+
}
|
|
12715
|
+
}
|
|
12716
|
+
async function retainedPrivateLessonOutboxBytes(directory) {
|
|
12717
|
+
const entries = await secureOutboxEntries(directory);
|
|
12718
|
+
let total = 0;
|
|
12719
|
+
for (const name of entries)
|
|
12720
|
+
total += (await lstat(join4(directory, name))).size;
|
|
12721
|
+
return total;
|
|
12722
|
+
}
|
|
12723
|
+
async function reconcileExplicitDeletionTombstones(directory) {
|
|
12724
|
+
const entries = await secureOutboxEntries(directory);
|
|
12725
|
+
let changed = false;
|
|
12726
|
+
for (const name of entries.filter(
|
|
12727
|
+
(entry) => entry.endsWith(".deleted.json")
|
|
12728
|
+
)) {
|
|
12729
|
+
const tombstone = parseDeletionTombstone(
|
|
12730
|
+
await readBoundedFile(join4(directory, name), 4 * 1024),
|
|
12731
|
+
name.slice(0, -".deleted.json".length)
|
|
12732
|
+
);
|
|
12733
|
+
const retainedDraft = draftPath(directory, tombstone.draft_id);
|
|
12734
|
+
if (!existsSync4(retainedDraft)) continue;
|
|
12735
|
+
await assertRegularPrivateFile(
|
|
12736
|
+
retainedDraft,
|
|
12737
|
+
PRIVATE_LESSON_DRAFT_MAX_BYTES
|
|
12738
|
+
);
|
|
12739
|
+
await unlink(retainedDraft);
|
|
12740
|
+
changed = true;
|
|
12741
|
+
}
|
|
12742
|
+
if (changed) await syncDirectory(directory);
|
|
12743
|
+
}
|
|
12744
|
+
async function syncDirectory(directory) {
|
|
12745
|
+
if (process.platform === "win32") return;
|
|
12746
|
+
const handle = await open(directory, "r");
|
|
12747
|
+
try {
|
|
12748
|
+
await handle.sync();
|
|
12749
|
+
} finally {
|
|
12750
|
+
await handle.close();
|
|
12751
|
+
}
|
|
12752
|
+
}
|
|
12753
|
+
async function writeDraft(draft, outbox, replace = false, currentOutboxBytes, allowUnsupportedProfile = false) {
|
|
12754
|
+
return withPrivateLessonOutboxLock(outbox.directory, async () => {
|
|
12755
|
+
const validated = validateDraft(draft, allowUnsupportedProfile);
|
|
12756
|
+
await writeDraftUnlocked(
|
|
12757
|
+
validated,
|
|
12758
|
+
outbox,
|
|
12759
|
+
replace,
|
|
12760
|
+
currentOutboxBytes,
|
|
12761
|
+
allowUnsupportedProfile
|
|
12762
|
+
);
|
|
12763
|
+
});
|
|
12764
|
+
}
|
|
12765
|
+
async function writeDraftUnlocked(draft, outbox, replace, _currentOutboxBytes, allowUnsupportedProfile = false) {
|
|
12766
|
+
const validated = validateDraft(draft, allowUnsupportedProfile);
|
|
12767
|
+
const plaintext = Buffer.from(canonicalJson(validated), "utf8");
|
|
12768
|
+
if (plaintext.length > PRIVATE_LESSON_DRAFT_MAX_BYTES) {
|
|
12769
|
+
throw new Error("Private lesson draft exceeds its local size limit.");
|
|
12770
|
+
}
|
|
12771
|
+
const nonce = randomBytes2(12);
|
|
12772
|
+
const aad = Buffer.from(
|
|
12773
|
+
canonicalJson({
|
|
12774
|
+
version: "private-lesson-outbox-envelope-v1",
|
|
12775
|
+
draft_id: validated.draft_id,
|
|
12776
|
+
state: validated.state,
|
|
12777
|
+
payload_digest: validated.payload_digest,
|
|
12778
|
+
hold_telemetry_status: validated.hold_telemetry_status,
|
|
12779
|
+
next_retry_at: validated.next_retry_at,
|
|
12780
|
+
terminal_reason: validated.terminal_reason
|
|
12781
|
+
}),
|
|
12782
|
+
"utf8"
|
|
12783
|
+
);
|
|
12784
|
+
const cipher = createCipheriv("aes-256-gcm", outbox.encryption.key, nonce);
|
|
12785
|
+
cipher.setAAD(aad);
|
|
12786
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
12787
|
+
const envelope = {
|
|
12788
|
+
version: "private-lesson-outbox-envelope-v1",
|
|
12789
|
+
draft_id: validated.draft_id,
|
|
12790
|
+
state: validated.state,
|
|
12791
|
+
payload_digest: validated.payload_digest,
|
|
12792
|
+
hold_telemetry_status: validated.hold_telemetry_status,
|
|
12793
|
+
next_retry_at: validated.next_retry_at,
|
|
12794
|
+
terminal_reason: validated.terminal_reason,
|
|
12795
|
+
created_at: validated.created_at,
|
|
12796
|
+
updated_at: validated.updated_at,
|
|
12797
|
+
encryption: {
|
|
12798
|
+
algorithm: "aes-256-gcm",
|
|
12799
|
+
key_source: outbox.encryption.source,
|
|
12800
|
+
nonce: nonce.toString("base64url"),
|
|
12801
|
+
ciphertext: ciphertext.toString("base64url"),
|
|
12802
|
+
auth_tag: cipher.getAuthTag().toString("base64url")
|
|
12803
|
+
}
|
|
12804
|
+
};
|
|
12805
|
+
const serialized = `${JSON.stringify(envelope)}
|
|
12806
|
+
`;
|
|
12807
|
+
if (!replace && await retainedPrivateLessonOutboxBytes(outbox.directory) + Buffer.byteLength(serialized, "utf8") > outbox.ceilingBytes) {
|
|
12808
|
+
throw new Error(
|
|
12809
|
+
"Private lesson capture is paused because the local 64 MiB outbox safety ceiling was reached."
|
|
12810
|
+
);
|
|
12811
|
+
}
|
|
12812
|
+
const path = draftPath(outbox.directory, validated.draft_id);
|
|
12813
|
+
if (replace) {
|
|
12814
|
+
await atomicReplacePrivate(path, serialized);
|
|
12815
|
+
} else {
|
|
12816
|
+
await atomicWritePrivate(path, serialized);
|
|
12817
|
+
}
|
|
12818
|
+
}
|
|
12819
|
+
function decryptDraft(envelope, encryption, allowUnsupportedProfile = false) {
|
|
12820
|
+
const aad = Buffer.from(
|
|
12821
|
+
canonicalJson({
|
|
12822
|
+
version: envelope.version,
|
|
12823
|
+
draft_id: envelope.draft_id,
|
|
12824
|
+
state: envelope.state,
|
|
12825
|
+
payload_digest: envelope.payload_digest,
|
|
12826
|
+
hold_telemetry_status: envelope.hold_telemetry_status,
|
|
12827
|
+
next_retry_at: envelope.next_retry_at,
|
|
12828
|
+
terminal_reason: envelope.terminal_reason
|
|
12829
|
+
}),
|
|
12830
|
+
"utf8"
|
|
12831
|
+
);
|
|
12832
|
+
const decipher = createDecipheriv(
|
|
12833
|
+
"aes-256-gcm",
|
|
12834
|
+
encryption.key,
|
|
12835
|
+
Buffer.from(envelope.encryption.nonce, "base64url")
|
|
12836
|
+
);
|
|
12837
|
+
decipher.setAAD(aad);
|
|
12838
|
+
decipher.setAuthTag(Buffer.from(envelope.encryption.auth_tag, "base64url"));
|
|
12839
|
+
const plaintext = Buffer.concat([
|
|
12840
|
+
decipher.update(Buffer.from(envelope.encryption.ciphertext, "base64url")),
|
|
12841
|
+
decipher.final()
|
|
12842
|
+
]).toString("utf8");
|
|
12843
|
+
const draft = validateDraft(JSON.parse(plaintext), allowUnsupportedProfile);
|
|
12844
|
+
if (draft.draft_id !== envelope.draft_id || draft.state !== envelope.state || draft.payload_digest !== envelope.payload_digest || draft.hold_telemetry_status !== envelope.hold_telemetry_status || draft.next_retry_at !== envelope.next_retry_at || draft.terminal_reason !== envelope.terminal_reason) {
|
|
12845
|
+
throw new Error(
|
|
12846
|
+
"Private lesson outbox envelope does not match its payload."
|
|
12847
|
+
);
|
|
12848
|
+
}
|
|
12849
|
+
return draft;
|
|
12850
|
+
}
|
|
12851
|
+
function validateDraft(value, allowUnsupportedProfile = false) {
|
|
12852
|
+
if (!isRecord4(value)) throw new Error("Private lesson draft is invalid.");
|
|
12853
|
+
assertDraftId(String(value.draft_id ?? ""));
|
|
12854
|
+
if (value.version !== PRIVATE_LESSON_OUTBOX_VERSION) {
|
|
12855
|
+
throw new Error("Private lesson draft version is unsupported.");
|
|
12856
|
+
}
|
|
12857
|
+
if (!isOutboxState(value.state)) {
|
|
12858
|
+
throw new Error("Private lesson draft state is invalid.");
|
|
12859
|
+
}
|
|
12860
|
+
if (typeof value.canonical_payload !== "string") {
|
|
12861
|
+
throw new Error("Private lesson canonical payload is missing.");
|
|
12862
|
+
}
|
|
12863
|
+
if (hashValue(value.canonical_payload) !== value.payload_digest) {
|
|
12864
|
+
throw new Error("Private lesson draft payload digest is invalid.");
|
|
12865
|
+
}
|
|
12866
|
+
if (!isRecord4(value.candidate)) {
|
|
12867
|
+
throw new Error("Private lesson draft candidate is missing.");
|
|
12868
|
+
}
|
|
12869
|
+
if (canonicalJson(value.candidate) !== value.canonical_payload) {
|
|
12870
|
+
throw new Error("Private lesson draft candidate is not canonical.");
|
|
12871
|
+
}
|
|
12872
|
+
if (typeof value.redaction_version !== "string" || typeof value.redactor_digest !== "string" || value.candidate.redaction_version !== value.redaction_version) {
|
|
12873
|
+
throw new Error("Private lesson draft redactor profile is invalid.");
|
|
12874
|
+
}
|
|
12875
|
+
try {
|
|
12876
|
+
privateLessonRedactorProfile(
|
|
12877
|
+
value.redaction_version,
|
|
12878
|
+
value.redactor_digest
|
|
12879
|
+
);
|
|
12880
|
+
if (canonicalPrivateLessonPayload(value.candidate) !== value.canonical_payload) {
|
|
12881
|
+
throw new Error("Private lesson draft candidate is not canonical.");
|
|
12882
|
+
}
|
|
12883
|
+
} catch (error) {
|
|
12884
|
+
if (!(error instanceof PrivateLessonRedactionProfileError) || !allowUnsupportedProfile) {
|
|
12885
|
+
throw error;
|
|
12886
|
+
}
|
|
12887
|
+
}
|
|
12888
|
+
if (!Number.isInteger(value.attempts) || Number(value.attempts) < 0 || Number(value.attempts) > 1e3) {
|
|
12889
|
+
throw new Error("Private lesson draft attempts are invalid.");
|
|
12890
|
+
}
|
|
12891
|
+
if (!isHoldTelemetryStatus(value.hold_telemetry_status)) {
|
|
12892
|
+
throw new Error("Private lesson hold telemetry status is invalid.");
|
|
12893
|
+
}
|
|
12894
|
+
if (value.terminal_reason !== null && typeof value.terminal_reason !== "string") {
|
|
12895
|
+
throw new Error("Private lesson terminal reason is invalid.");
|
|
12896
|
+
}
|
|
12897
|
+
if (value.state === "superseded_redactor" && value.terminal_reason !== "unsupported_redaction_version" && value.terminal_reason !== "unsupported_redactor_digest") {
|
|
12898
|
+
throw new Error("Private lesson terminal reason is unsupported.");
|
|
12899
|
+
}
|
|
12900
|
+
if (value.state !== "superseded_redactor" && value.terminal_reason !== null) {
|
|
12901
|
+
throw new Error("Only terminal drafts may include a terminal reason.");
|
|
12902
|
+
}
|
|
12903
|
+
if (value.state === "superseded_redactor" && (value.next_retry_at !== null || value.hold_telemetry_status !== "disabled" && value.hold_telemetry_status !== "submitted")) {
|
|
12904
|
+
throw new Error("Terminal private lesson drafts cannot be retried.");
|
|
12905
|
+
}
|
|
12906
|
+
if (value.policy !== null) {
|
|
12907
|
+
if (!isRecord4(value.policy) || typeof value.policy.policy_digest !== "string" || !Number.isSafeInteger(value.policy.policy_sequence) || typeof value.policy.organization_audience_hash !== "string" || typeof value.policy.expires_at !== "string" || !Array.isArray(value.policy.enabled_tiers) || typeof value.policy.redaction_version !== "string" || typeof value.policy.redactor_digest !== "string") {
|
|
12908
|
+
throw new Error("Private lesson draft policy binding is invalid.");
|
|
12909
|
+
}
|
|
12910
|
+
privateLessonRedactorProfile(
|
|
12911
|
+
value.policy.redaction_version,
|
|
12912
|
+
value.policy.redactor_digest
|
|
12913
|
+
);
|
|
12914
|
+
}
|
|
12915
|
+
return value;
|
|
12916
|
+
}
|
|
12917
|
+
function terminalizeUnsupportedProfile(draft, now) {
|
|
12918
|
+
try {
|
|
12919
|
+
privateLessonRedactorProfile(
|
|
12920
|
+
draft.redaction_version,
|
|
12921
|
+
draft.redactor_digest
|
|
12922
|
+
);
|
|
12923
|
+
return draft;
|
|
12924
|
+
} catch (error) {
|
|
12925
|
+
if (!(error instanceof PrivateLessonRedactionProfileError)) throw error;
|
|
12926
|
+
if (draft.state === "superseded_redactor" && draft.terminal_reason === error.code) {
|
|
12927
|
+
return draft;
|
|
12928
|
+
}
|
|
12929
|
+
return {
|
|
12930
|
+
...draft,
|
|
12931
|
+
state: "superseded_redactor",
|
|
12932
|
+
terminal_reason: error.code,
|
|
12933
|
+
next_retry_at: null,
|
|
12934
|
+
hold_telemetry_status: draft.hold_telemetry_status === "submitted" ? "submitted" : "disabled",
|
|
12935
|
+
updated_at: now.toISOString()
|
|
12936
|
+
};
|
|
12937
|
+
}
|
|
12938
|
+
}
|
|
12939
|
+
function isHoldTelemetryStatus(value) {
|
|
12940
|
+
return value === "not_required" || value === "pending" || value === "awaiting_authorization" || value === "retry_scheduled" || value === "submitted" || value === "disabled";
|
|
12941
|
+
}
|
|
12942
|
+
function parseEnvelope(serialized, expectedDraftId) {
|
|
12943
|
+
const value = JSON.parse(serialized);
|
|
12944
|
+
if (!isRecord4(value) || value.version !== "private-lesson-outbox-envelope-v1" || value.draft_id !== expectedDraftId || !isOutboxState(value.state) || typeof value.payload_digest !== "string" || !isHoldTelemetryStatus(value.hold_telemetry_status) || value.next_retry_at !== null && typeof value.next_retry_at !== "string" || value.terminal_reason !== null && typeof value.terminal_reason !== "string" || !isRecord4(value.encryption) || value.encryption.algorithm !== "aes-256-gcm" || value.encryption.key_source !== "os_keychain" && value.encryption.key_source !== "mode_0600_file" || typeof value.encryption.nonce !== "string" || typeof value.encryption.ciphertext !== "string" || typeof value.encryption.auth_tag !== "string") {
|
|
12945
|
+
throw new Error("Private lesson outbox envelope is invalid.");
|
|
12946
|
+
}
|
|
12947
|
+
return value;
|
|
12948
|
+
}
|
|
12949
|
+
function parseDeletionTombstone(serialized, expectedDraftId) {
|
|
12950
|
+
const value = JSON.parse(serialized);
|
|
12951
|
+
if (!isRecord4(value) || value.version !== "private-lesson-outbox-deletion-v1" || value.draft_id !== expectedDraftId || typeof value.deleted_at !== "string" || value.reason !== "explicit_confirmed_deletion") {
|
|
12952
|
+
throw new Error("Private lesson deletion tombstone is invalid.");
|
|
12953
|
+
}
|
|
12954
|
+
return value;
|
|
12955
|
+
}
|
|
12956
|
+
async function secureOutboxEntries(directory) {
|
|
12957
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
12958
|
+
assertSecurePrivateLessonOutboxEntries(entries);
|
|
12959
|
+
return entries.filter((entry) => entry.isFile() && !entry.name.startsWith(".")).map((entry) => entry.name).sort();
|
|
12960
|
+
}
|
|
12961
|
+
async function assertRegularPrivateFile(path, maxBytes) {
|
|
12962
|
+
const metadata = await lstat(path);
|
|
12963
|
+
assertPrivateLessonPathMetadata(metadata, "file");
|
|
12964
|
+
if (metadata.size > maxBytes) {
|
|
12965
|
+
throw new Error("Private lesson outbox entry exceeds its size limit.");
|
|
12966
|
+
}
|
|
12967
|
+
}
|
|
12968
|
+
async function readBoundedFile(path, maxBytes) {
|
|
12969
|
+
return readSecureLocalText(path, { maxBytes });
|
|
12970
|
+
}
|
|
12971
|
+
async function atomicWritePrivate(path, data) {
|
|
12972
|
+
const handle = await open(path, "wx", 384);
|
|
12973
|
+
try {
|
|
12974
|
+
await handle.writeFile(data, "utf8");
|
|
12975
|
+
await handle.sync();
|
|
12976
|
+
await handle.chmod(384);
|
|
12977
|
+
} finally {
|
|
12978
|
+
await handle.close();
|
|
12979
|
+
}
|
|
12980
|
+
await syncDirectory(dirname2(path));
|
|
12981
|
+
}
|
|
12982
|
+
async function atomicReplacePrivate(path, data) {
|
|
12983
|
+
await assertRegularPrivateFile(path, PRIVATE_LESSON_DRAFT_MAX_BYTES);
|
|
12984
|
+
const temporary = join4(
|
|
12985
|
+
dirname2(path),
|
|
12986
|
+
`.${randomBytes2(12).toString("hex")}.tmp`
|
|
12987
|
+
);
|
|
12988
|
+
try {
|
|
12989
|
+
await atomicWritePrivate(temporary, data);
|
|
12990
|
+
await rename(temporary, path);
|
|
12991
|
+
await chmod2(path, 384);
|
|
12992
|
+
await syncDirectory(dirname2(path));
|
|
12993
|
+
} finally {
|
|
12994
|
+
await unlink(temporary).catch(() => {
|
|
12995
|
+
});
|
|
12996
|
+
}
|
|
12997
|
+
}
|
|
12998
|
+
function readExistingOutboxEncryption(env, options) {
|
|
12999
|
+
const keychain = readPrivateLessonKeychainKey(env);
|
|
13000
|
+
if (keychain) return { key: keychain, source: "os_keychain" };
|
|
13001
|
+
const path = privateLessonOutboxKeyPath(env, options.homeDirectory);
|
|
13002
|
+
if (!existsSync4(path)) return null;
|
|
13003
|
+
try {
|
|
13004
|
+
const encoded = readSecureLocalText(path, { maxBytes: 256 }).trim();
|
|
13005
|
+
const key = Buffer.from(encoded, "base64url");
|
|
13006
|
+
return key.length === 32 ? { key, source: "mode_0600_file" } : null;
|
|
13007
|
+
} catch {
|
|
13008
|
+
return null;
|
|
13009
|
+
}
|
|
13010
|
+
}
|
|
13011
|
+
function assertPrivateLessonPathMetadata(metadata, kind, currentUid = typeof process.getuid === "function" ? process.getuid() : null, platform = process.platform) {
|
|
13012
|
+
const validType = !metadata.isSymbolicLink() && (kind === "directory" ? metadata.isDirectory() : metadata.isFile());
|
|
13013
|
+
if (!validType) {
|
|
13014
|
+
throw new Error(
|
|
13015
|
+
kind === "directory" ? "Private lesson outbox path is unsafe." : "Private lesson outbox entry is unsafe."
|
|
13016
|
+
);
|
|
13017
|
+
}
|
|
13018
|
+
if (currentUid !== null && metadata.uid !== currentUid) {
|
|
13019
|
+
throw new Error(
|
|
13020
|
+
kind === "directory" ? "Private lesson outbox is not owned by the current user." : "Private lesson outbox entry is not owned by the current user."
|
|
13021
|
+
);
|
|
13022
|
+
}
|
|
13023
|
+
if (platform !== "win32" && (metadata.mode & 63) !== 0) {
|
|
13024
|
+
throw new Error(
|
|
13025
|
+
kind === "directory" ? "Private lesson outbox permissions are not private." : "Private lesson outbox entry permissions are not private."
|
|
13026
|
+
);
|
|
13027
|
+
}
|
|
13028
|
+
}
|
|
13029
|
+
function assertSecurePrivateLessonOutboxEntries(entries) {
|
|
13030
|
+
if (entries.some((entry) => entry.isSymbolicLink() || !entry.isFile())) {
|
|
13031
|
+
throw new Error("Private lesson outbox contains an unsafe entry.");
|
|
13032
|
+
}
|
|
13033
|
+
}
|
|
13034
|
+
function emptyOutboxCounts() {
|
|
13035
|
+
return {
|
|
13036
|
+
ready: 0,
|
|
13037
|
+
awaiting_authorization: 0,
|
|
13038
|
+
retry_scheduled: 0,
|
|
13039
|
+
held_safety: 0,
|
|
13040
|
+
submitted: 0,
|
|
13041
|
+
superseded_redactor: 0
|
|
13042
|
+
};
|
|
13043
|
+
}
|
|
13044
|
+
function draftPath(directory, draftId) {
|
|
13045
|
+
assertDraftId(draftId);
|
|
13046
|
+
return join4(directory, `${draftId}.lesson.json`);
|
|
13047
|
+
}
|
|
13048
|
+
function tombstonePath(directory, draftId) {
|
|
13049
|
+
assertDraftId(draftId);
|
|
13050
|
+
return join4(directory, `${draftId}.deleted.json`);
|
|
13051
|
+
}
|
|
13052
|
+
function assertDraftId(value) {
|
|
13053
|
+
if (!/^pld_[A-Za-z0-9_-]{8,96}$/.test(value)) {
|
|
13054
|
+
throw new Error("Private lesson draft id is invalid.");
|
|
13055
|
+
}
|
|
13056
|
+
}
|
|
13057
|
+
function isOutboxState(value) {
|
|
13058
|
+
return value === "ready" || value === "awaiting_authorization" || value === "retry_scheduled" || value === "held_safety" || value === "submitted" || value === "superseded_redactor";
|
|
13059
|
+
}
|
|
13060
|
+
function isRecord4(value) {
|
|
13061
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
13062
|
+
}
|
|
13063
|
+
function digestHex(value, label) {
|
|
13064
|
+
const match = value.match(/^sha256:([a-f0-9]{64})$/);
|
|
13065
|
+
if (!match) {
|
|
13066
|
+
throw new PrivateLessonPolicyFloorError(
|
|
13067
|
+
`Private lesson ${label} digest is invalid.`,
|
|
13068
|
+
"policy_floor_invalid"
|
|
13069
|
+
);
|
|
13070
|
+
}
|
|
13071
|
+
return match[1];
|
|
13072
|
+
}
|
|
13073
|
+
function isMissingError(error) {
|
|
13074
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
13075
|
+
}
|
|
13076
|
+
function isAlreadyExistsError2(error) {
|
|
13077
|
+
return error instanceof Error && "code" in error && error.code === "EEXIST";
|
|
13078
|
+
}
|
|
13079
|
+
async function readConcurrentPrivateLessonFallbackKey(path) {
|
|
13080
|
+
let lastError = new Error(
|
|
13081
|
+
"Private lesson outbox encryption key is unavailable."
|
|
13082
|
+
);
|
|
13083
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
13084
|
+
try {
|
|
13085
|
+
await assertRegularPrivateFile(path, 256);
|
|
13086
|
+
const encoded = (await readBoundedFile(path, 256)).trim();
|
|
13087
|
+
if (Buffer.from(encoded, "base64url").length === 32) return encoded;
|
|
13088
|
+
lastError = new Error("Private lesson outbox encryption key is invalid.");
|
|
13089
|
+
} catch (error) {
|
|
13090
|
+
lastError = error;
|
|
13091
|
+
}
|
|
13092
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
13093
|
+
}
|
|
13094
|
+
throw lastError;
|
|
13095
|
+
}
|
|
13096
|
+
|
|
13097
|
+
// src/server.ts
|
|
11084
13098
|
var MAX_REMOTE_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
11085
13099
|
var DOCTOR_PROBE_TIMEOUT_MS = 7500;
|
|
11086
|
-
var SERVER_VERSION = true ? "0.1.
|
|
13100
|
+
var SERVER_VERSION = true ? "0.1.71" : "0.0.0-dev";
|
|
11087
13101
|
var tools = toolDefinitions;
|
|
11088
13102
|
var doctorCliRequested = process.argv[2] === "doctor";
|
|
11089
13103
|
var inputBuffer = Buffer.alloc(0);
|
|
@@ -11091,6 +13105,7 @@ var clientFraming = "ndjson";
|
|
|
11091
13105
|
var cachedPrincipalSession = null;
|
|
11092
13106
|
var connectedClientInfo = null;
|
|
11093
13107
|
var cachedValueProofKeys = null;
|
|
13108
|
+
var principalSessionBackgroundTasks = /* @__PURE__ */ new Set();
|
|
11094
13109
|
var PRINCIPAL_SESSION_REQUIRED_TOOLS = /* @__PURE__ */ new Set([
|
|
11095
13110
|
"record_preference",
|
|
11096
13111
|
"submit_preference_compatibility_feedback",
|
|
@@ -11114,6 +13129,11 @@ function apiAccessKey(access) {
|
|
|
11114
13129
|
function resetPrincipalSessionCacheForTests() {
|
|
11115
13130
|
cachedPrincipalSession = null;
|
|
11116
13131
|
}
|
|
13132
|
+
async function waitForPrincipalSessionBackgroundTasksForTests() {
|
|
13133
|
+
while (principalSessionBackgroundTasks.size > 0) {
|
|
13134
|
+
await Promise.allSettled([...principalSessionBackgroundTasks]);
|
|
13135
|
+
}
|
|
13136
|
+
}
|
|
11117
13137
|
if (!doctorCliRequested) {
|
|
11118
13138
|
process.stdin.on("data", (chunk) => {
|
|
11119
13139
|
inputBuffer = Buffer.concat([inputBuffer, chunk]);
|
|
@@ -11205,7 +13225,11 @@ async function dispatchJsonRpcRequest(request) {
|
|
|
11205
13225
|
if (request.method === "initialize") {
|
|
11206
13226
|
connectedClientInfo = normalizedMcpClientInfo(request.params?.clientInfo);
|
|
11207
13227
|
const access = resolveApiAccess();
|
|
11208
|
-
|
|
13228
|
+
warmPrincipalSession(access);
|
|
13229
|
+
if (process.env.NODE_ENV !== "test") {
|
|
13230
|
+
void inspectPrivateLessonOutboxHealth().catch(() => {
|
|
13231
|
+
});
|
|
13232
|
+
}
|
|
11209
13233
|
const clientUpdate = await currentClientUpdate(access);
|
|
11210
13234
|
return {
|
|
11211
13235
|
id: request.id,
|
|
@@ -11332,6 +13356,23 @@ async function callTool(definition, rawArguments) {
|
|
|
11332
13356
|
uploadBaseUrl: access.configuration.baseUrl
|
|
11333
13357
|
});
|
|
11334
13358
|
}
|
|
13359
|
+
if (definition.local === "prepare_private_lesson_candidate") {
|
|
13360
|
+
return preparePrivateLessonDraft(payload);
|
|
13361
|
+
}
|
|
13362
|
+
if (definition.local === "inspect_private_lesson_outbox") {
|
|
13363
|
+
return inspectPrivateLessonOutbox(payload);
|
|
13364
|
+
}
|
|
13365
|
+
if (definition.local === "retry_private_lesson_candidate") {
|
|
13366
|
+
return submitPrivateLessonDraft(
|
|
13367
|
+
String(payload.draft_id),
|
|
13368
|
+
access,
|
|
13369
|
+
true
|
|
13370
|
+
);
|
|
13371
|
+
}
|
|
13372
|
+
if (definition.local === "delete_private_lesson_candidate") {
|
|
13373
|
+
const request = payload;
|
|
13374
|
+
return deletePrivateLessonDraft(request.draft_id, request.confirm);
|
|
13375
|
+
}
|
|
11335
13376
|
if (definition.name === "run_connection_doctor") {
|
|
11336
13377
|
return runLocalConnectionDoctor(
|
|
11337
13378
|
payload,
|
|
@@ -11345,6 +13386,12 @@ async function callTool(definition, rawArguments) {
|
|
|
11345
13386
|
if (definition.name === "submit_remembrance") {
|
|
11346
13387
|
return submitRemembrance(payload, access);
|
|
11347
13388
|
}
|
|
13389
|
+
if (definition.name === "get_private_lesson_policy") {
|
|
13390
|
+
return getLocalPrivateLessonPolicy(access);
|
|
13391
|
+
}
|
|
13392
|
+
if (definition.name === "submit_private_lesson_candidate" && isRecord5(payload) && typeof payload.draft_id === "string") {
|
|
13393
|
+
return submitPrivateLessonDraft(payload.draft_id, access, false);
|
|
13394
|
+
}
|
|
11348
13395
|
const localConfigurationError = remoteConfigurationError(
|
|
11349
13396
|
access.configuration.source,
|
|
11350
13397
|
access.credential.source,
|
|
@@ -11386,7 +13433,7 @@ function apiConfigurationErrorMessage(issue, setting) {
|
|
|
11386
13433
|
return `${setting} is invalid. Set it to an absolute HTTP(S) registry URL without credentials, query parameters, or fragments, or intentionally remove it before using remote Remembrance tools.`;
|
|
11387
13434
|
}
|
|
11388
13435
|
function clientHealthReportFromConnectionStatus(status) {
|
|
11389
|
-
const health =
|
|
13436
|
+
const health = isRecord5(status.plugin_health) ? status.plugin_health : null;
|
|
11390
13437
|
if (!health || health.expected !== true || health.status !== "degraded") {
|
|
11391
13438
|
return null;
|
|
11392
13439
|
}
|
|
@@ -11396,9 +13443,9 @@ function clientHealthReportFromConnectionStatus(status) {
|
|
|
11396
13443
|
}
|
|
11397
13444
|
const host = agentHostBySurface(surface);
|
|
11398
13445
|
if (!host || !host.plugin) return null;
|
|
11399
|
-
const components =
|
|
11400
|
-
const observed = (name) =>
|
|
11401
|
-
const issueCodes2 = Array.isArray(health.issues) ? health.issues.map((issue) =>
|
|
13446
|
+
const components = isRecord5(health.components) ? health.components : {};
|
|
13447
|
+
const observed = (name) => isRecord5(components[name]) && components[name]?.observed === true ? "active" : "not_observed";
|
|
13448
|
+
const issueCodes2 = Array.isArray(health.issues) ? health.issues.map((issue) => isRecord5(issue) ? issue.code : null).filter((code) => typeof code === "string") : [];
|
|
11402
13449
|
const allowedIssueCodes = /* @__PURE__ */ new Set([
|
|
11403
13450
|
"native_hooks_not_observed",
|
|
11404
13451
|
"session_start_not_observed",
|
|
@@ -11451,7 +13498,7 @@ function isSafeHostVersion(value) {
|
|
|
11451
13498
|
return value === null || typeof value === "string" && value.length <= 64 && /^[0-9A-Za-z._+ -]*$/.test(value.trim());
|
|
11452
13499
|
}
|
|
11453
13500
|
async function reportDegradedClientHealth(status, access = resolveApiAccess()) {
|
|
11454
|
-
if (
|
|
13501
|
+
if (!privateLessonHealthReportingEnabled()) {
|
|
11455
13502
|
return;
|
|
11456
13503
|
}
|
|
11457
13504
|
const report = clientHealthReportFromConnectionStatus(status);
|
|
@@ -11477,6 +13524,734 @@ async function reportDegradedClientHealth(status, access = resolveApiAccess()) {
|
|
|
11477
13524
|
clearTimeout(timeout);
|
|
11478
13525
|
}
|
|
11479
13526
|
}
|
|
13527
|
+
var PrivateLessonPolicySafetyError = class extends Error {
|
|
13528
|
+
};
|
|
13529
|
+
var PrivateLessonPolicyRefreshRequiredError = class extends Error {
|
|
13530
|
+
};
|
|
13531
|
+
var PrivateLessonRemoteError = class extends McpPublicError {
|
|
13532
|
+
constructor(message, status, code, retryable) {
|
|
13533
|
+
super(message);
|
|
13534
|
+
this.status = status;
|
|
13535
|
+
this.code = code;
|
|
13536
|
+
this.retryable = retryable;
|
|
13537
|
+
}
|
|
13538
|
+
status;
|
|
13539
|
+
code;
|
|
13540
|
+
retryable;
|
|
13541
|
+
};
|
|
13542
|
+
async function getLocalPrivateLessonPolicy(access) {
|
|
13543
|
+
const verified = await fetchVerifiedPrivateLessonPolicy(access);
|
|
13544
|
+
const outbox = await privateLessonOutboxSummary();
|
|
13545
|
+
return {
|
|
13546
|
+
ok: true,
|
|
13547
|
+
status: 200,
|
|
13548
|
+
body: {
|
|
13549
|
+
...verified.body,
|
|
13550
|
+
auto_capture_supported: true,
|
|
13551
|
+
local_outbox: publicOutboxSummary(outbox)
|
|
13552
|
+
}
|
|
13553
|
+
};
|
|
13554
|
+
}
|
|
13555
|
+
async function preparePrivateLessonDraft(input) {
|
|
13556
|
+
const prepared = preparePrivateLessonCandidate(input);
|
|
13557
|
+
const draft = await queuePreparedPrivateLesson(prepared, null);
|
|
13558
|
+
return privateLessonDraftActionResult(draft, {
|
|
13559
|
+
next_action: prepared.status === "ready" || prepared.status === "held_safety" && privateLessonHealthReportingEnabled() ? {
|
|
13560
|
+
tool: "submit_private_lesson_candidate",
|
|
13561
|
+
arguments: { draft_id: draft.draft_id }
|
|
13562
|
+
} : null
|
|
13563
|
+
});
|
|
13564
|
+
}
|
|
13565
|
+
async function inspectPrivateLessonOutbox(input) {
|
|
13566
|
+
const summary = await privateLessonOutboxSummary();
|
|
13567
|
+
if (input.draft_id) {
|
|
13568
|
+
const draft = await readPrivateLessonDraft(input.draft_id);
|
|
13569
|
+
return {
|
|
13570
|
+
draft: privateLessonDraftActionResult(draft),
|
|
13571
|
+
outbox: publicOutboxSummary(summary)
|
|
13572
|
+
};
|
|
13573
|
+
}
|
|
13574
|
+
return {
|
|
13575
|
+
drafts: await listPrivateLessonDrafts({ limit: input.limit }),
|
|
13576
|
+
outbox: publicOutboxSummary(summary)
|
|
13577
|
+
};
|
|
13578
|
+
}
|
|
13579
|
+
async function submitPrivateLessonDraft(draftId, access, allowPolicyRefresh) {
|
|
13580
|
+
let draft = await readPrivateLessonDraft(draftId);
|
|
13581
|
+
if (draft.state === "superseded_redactor") {
|
|
13582
|
+
return privateLessonDraftActionResult(draft, {
|
|
13583
|
+
status: "superseded_redactor",
|
|
13584
|
+
action_required: "This terminal draft uses an unsupported redactor profile. Inspect it, then explicitly delete it only if you no longer need to retain it."
|
|
13585
|
+
});
|
|
13586
|
+
}
|
|
13587
|
+
if (draft.state === "submitted") {
|
|
13588
|
+
return privateLessonDraftActionResult(draft, {
|
|
13589
|
+
status: "already_submitted"
|
|
13590
|
+
});
|
|
13591
|
+
}
|
|
13592
|
+
if (draft.state === "held_safety") {
|
|
13593
|
+
return submitPrivateLessonHoldTelemetry(draft, access, allowPolicyRefresh);
|
|
13594
|
+
}
|
|
13595
|
+
let verifiedPolicy;
|
|
13596
|
+
let currentBinding;
|
|
13597
|
+
try {
|
|
13598
|
+
verifiedPolicy = await fetchVerifiedPrivateLessonPolicy(access);
|
|
13599
|
+
currentBinding = bindingForPrivateLessonPolicy(verifiedPolicy.policy);
|
|
13600
|
+
await rememberPrivateLessonPolicyFloor(currentBinding);
|
|
13601
|
+
} catch (error) {
|
|
13602
|
+
draft = await persistPrivateLessonFailure(
|
|
13603
|
+
draft,
|
|
13604
|
+
error,
|
|
13605
|
+
error instanceof PrivateLessonPolicyFloorError ? error.code : void 0
|
|
13606
|
+
);
|
|
13607
|
+
return privateLessonDraftActionResult(draft, {
|
|
13608
|
+
status: "not_submitted",
|
|
13609
|
+
action_required: draft.state === "awaiting_authorization" ? "Authorize the exact submit_private_lesson_candidate action, then retry." : null
|
|
13610
|
+
});
|
|
13611
|
+
}
|
|
13612
|
+
if (!privateLessonPolicyAllows(verifiedPolicy.policy, draft.candidate.tier)) {
|
|
13613
|
+
draft = await persistPrivateLessonFailure(
|
|
13614
|
+
draft,
|
|
13615
|
+
new PrivateLessonPolicySafetyError(
|
|
13616
|
+
"Organization private lesson policy does not allow this candidate."
|
|
13617
|
+
)
|
|
13618
|
+
);
|
|
13619
|
+
return privateLessonDraftActionResult(draft, { status: "not_submitted" });
|
|
13620
|
+
}
|
|
13621
|
+
if (draft.policy && draft.policy.organization_audience_hash !== currentBinding.organization_audience_hash) {
|
|
13622
|
+
draft = await persistPrivateLessonFailure(
|
|
13623
|
+
draft,
|
|
13624
|
+
new PrivateLessonPolicySafetyError(
|
|
13625
|
+
"Private lesson policy audience changed after preparation."
|
|
13626
|
+
),
|
|
13627
|
+
"policy_audience_changed"
|
|
13628
|
+
);
|
|
13629
|
+
return privateLessonDraftActionResult(draft, {
|
|
13630
|
+
status: "not_submitted",
|
|
13631
|
+
action_required: "Keep this draft retained and prepare a new draft for the active organization."
|
|
13632
|
+
});
|
|
13633
|
+
}
|
|
13634
|
+
if (draft.policy && currentBinding.policy_sequence < draft.policy.policy_sequence) {
|
|
13635
|
+
draft = await persistPrivateLessonFailure(
|
|
13636
|
+
draft,
|
|
13637
|
+
new PrivateLessonPolicySafetyError(
|
|
13638
|
+
"Private lesson policy sequence moved backward."
|
|
13639
|
+
),
|
|
13640
|
+
"policy_downgrade"
|
|
13641
|
+
);
|
|
13642
|
+
return privateLessonDraftActionResult(draft, { status: "not_submitted" });
|
|
13643
|
+
}
|
|
13644
|
+
if (draft.policy && currentBinding.policy_sequence === draft.policy.policy_sequence && currentBinding.policy_digest !== draft.policy.policy_digest) {
|
|
13645
|
+
draft = await persistPrivateLessonFailure(
|
|
13646
|
+
draft,
|
|
13647
|
+
new PrivateLessonPolicySafetyError(
|
|
13648
|
+
"Private lesson policy content conflicts at the same sequence."
|
|
13649
|
+
),
|
|
13650
|
+
"policy_sequence_conflict"
|
|
13651
|
+
);
|
|
13652
|
+
return privateLessonDraftActionResult(draft, { status: "not_submitted" });
|
|
13653
|
+
}
|
|
13654
|
+
if (draft.policy && draft.policy.policy_digest !== currentBinding.policy_digest && !allowPolicyRefresh) {
|
|
13655
|
+
draft = await persistPrivateLessonFailure(
|
|
13656
|
+
draft,
|
|
13657
|
+
new PrivateLessonPolicySafetyError(
|
|
13658
|
+
"Organization private lesson policy changed after preparation."
|
|
13659
|
+
),
|
|
13660
|
+
"policy_changed"
|
|
13661
|
+
);
|
|
13662
|
+
return privateLessonDraftActionResult(draft, {
|
|
13663
|
+
status: "not_submitted",
|
|
13664
|
+
action_required: "Explicitly retry the retained draft under the current policy."
|
|
13665
|
+
});
|
|
13666
|
+
}
|
|
13667
|
+
if (!draft.policy) {
|
|
13668
|
+
draft = await updatePrivateLessonDraft(draft.draft_id, (current) => ({
|
|
13669
|
+
...current,
|
|
13670
|
+
policy: current.policy ?? currentBinding
|
|
13671
|
+
}));
|
|
13672
|
+
}
|
|
13673
|
+
try {
|
|
13674
|
+
const identity = await ensureSigningIdentity();
|
|
13675
|
+
const challengeResult = await callRemembrance(
|
|
13676
|
+
mustFindTool("request_attestation_challenge"),
|
|
13677
|
+
{
|
|
13678
|
+
provider: identity.provider,
|
|
13679
|
+
source_type: "private_lesson",
|
|
13680
|
+
agent_id: identity.subject,
|
|
13681
|
+
evidence_hash: draft.payload_digest,
|
|
13682
|
+
private_lesson: {
|
|
13683
|
+
key_id: identity.key_id,
|
|
13684
|
+
organization_audience_hash: currentBinding.organization_audience_hash,
|
|
13685
|
+
payload_digest: draft.payload_digest,
|
|
13686
|
+
policy_digest: currentBinding.policy_digest,
|
|
13687
|
+
redactor_digest: draft.redactor_digest
|
|
13688
|
+
}
|
|
13689
|
+
},
|
|
13690
|
+
{ access, skipEconomicsSession: true }
|
|
13691
|
+
);
|
|
13692
|
+
const challenge = requirePrivateLessonRemoteBody(
|
|
13693
|
+
challengeResult,
|
|
13694
|
+
"challenge"
|
|
13695
|
+
);
|
|
13696
|
+
const signingPayload = stringField(challenge, "signing_payload_canonical");
|
|
13697
|
+
const submission = {
|
|
13698
|
+
candidate: draft.candidate,
|
|
13699
|
+
payload_digest: draft.payload_digest,
|
|
13700
|
+
policy_digest: currentBinding.policy_digest,
|
|
13701
|
+
redactor_digest: draft.redactor_digest,
|
|
13702
|
+
idempotency_key: draft.idempotency_key,
|
|
13703
|
+
attestation: {
|
|
13704
|
+
version: "private-lesson-attestation-v1",
|
|
13705
|
+
purpose: "remembrance-private-lesson-submission",
|
|
13706
|
+
provider: identity.provider,
|
|
13707
|
+
challenge_id: stringField(challenge, "challenge_id"),
|
|
13708
|
+
key_id: identity.key_id,
|
|
13709
|
+
algorithm: "ed25519",
|
|
13710
|
+
nonce: stringField(challenge, "nonce"),
|
|
13711
|
+
audience: "remembrance-private-lesson",
|
|
13712
|
+
organization_audience_hash: currentBinding.organization_audience_hash,
|
|
13713
|
+
payload_digest: draft.payload_digest,
|
|
13714
|
+
policy_digest: currentBinding.policy_digest,
|
|
13715
|
+
redactor_digest: draft.redactor_digest,
|
|
13716
|
+
issued_at: stringField(challenge, "issued_at"),
|
|
13717
|
+
expires_at: stringField(challenge, "expires_at"),
|
|
13718
|
+
signature: signPayload(
|
|
13719
|
+
null,
|
|
13720
|
+
Buffer.from(signingPayload),
|
|
13721
|
+
createPrivateKey2(identity.private_key)
|
|
13722
|
+
).toString("base64url")
|
|
13723
|
+
}
|
|
13724
|
+
};
|
|
13725
|
+
const result = await callRemembrance(
|
|
13726
|
+
mustFindTool("submit_private_lesson_candidate"),
|
|
13727
|
+
submission,
|
|
13728
|
+
{ access }
|
|
13729
|
+
);
|
|
13730
|
+
const body = requirePrivateLessonRemoteBody(result, "submission");
|
|
13731
|
+
const status = String(body.status ?? "");
|
|
13732
|
+
if (status !== "accepted_private_candidate" && status !== "duplicate") {
|
|
13733
|
+
throw new PrivateLessonPolicySafetyError(
|
|
13734
|
+
"Private lesson submission returned an unsupported state."
|
|
13735
|
+
);
|
|
13736
|
+
}
|
|
13737
|
+
const receipt = await verifyPrivateLessonEnvelopeWithKeyRefresh(
|
|
13738
|
+
body.receipt,
|
|
13739
|
+
"receipt",
|
|
13740
|
+
access
|
|
13741
|
+
);
|
|
13742
|
+
const processingState = String(body.processing_state ?? "");
|
|
13743
|
+
const receiptPolicy = receipt.payload.policy_digest === currentBinding.policy_digest ? currentBinding : draft.policy?.policy_digest === receipt.payload.policy_digest ? draft.policy : null;
|
|
13744
|
+
if (receipt.payload.status !== "accepted_private_candidate" || receipt.payload.processing_state !== "queued_for_verification" || status === "accepted_private_candidate" && processingState !== "queued_for_verification" || status === "duplicate" && processingState !== "already_queued" || body.payload_digest !== receipt.payload.payload_digest || body.policy_digest !== receipt.payload.policy_digest || receipt.payload.payload_digest !== draft.payload_digest || receipt.payload.contract_version !== draft.candidate.contract_version || receipt.payload.redaction_version !== draft.redaction_version || receipt.payload.redactor_digest !== draft.redactor_digest || !receiptPolicy || receipt.payload.organization_audience_hash !== currentBinding.organization_audience_hash) {
|
|
13745
|
+
throw new PrivateLessonPolicySafetyError(
|
|
13746
|
+
"Private lesson receipt does not match the submitted draft."
|
|
13747
|
+
);
|
|
13748
|
+
}
|
|
13749
|
+
const receiptDigest = hashValue(canonicalJson(receipt));
|
|
13750
|
+
draft = await updatePrivateLessonDraft(draft.draft_id, (current) => ({
|
|
13751
|
+
...current,
|
|
13752
|
+
state: "submitted",
|
|
13753
|
+
attempts: Math.min(1e3, current.attempts + 1),
|
|
13754
|
+
next_retry_at: null,
|
|
13755
|
+
last_error_code: null,
|
|
13756
|
+
submitted_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13757
|
+
receipt_digest: receiptDigest,
|
|
13758
|
+
policy: receiptPolicy
|
|
13759
|
+
}));
|
|
13760
|
+
return {
|
|
13761
|
+
...privateLessonDraftActionResult(draft, { status }),
|
|
13762
|
+
receipt,
|
|
13763
|
+
processing_state: processingState
|
|
13764
|
+
};
|
|
13765
|
+
} catch (error) {
|
|
13766
|
+
draft = await persistPrivateLessonFailure(draft, error);
|
|
13767
|
+
return privateLessonDraftActionResult(draft, {
|
|
13768
|
+
status: "not_submitted",
|
|
13769
|
+
action_required: draft.state === "awaiting_authorization" ? "Authorize the exact submit_private_lesson_candidate action, then retry." : null
|
|
13770
|
+
});
|
|
13771
|
+
}
|
|
13772
|
+
}
|
|
13773
|
+
async function submitPrivateLessonHoldTelemetry(initialDraft, access, allowPolicyRefresh) {
|
|
13774
|
+
let draft = initialDraft;
|
|
13775
|
+
if (draft.hold_telemetry_status === "submitted") {
|
|
13776
|
+
return privateLessonDraftActionResult(draft, {
|
|
13777
|
+
status: "already_recorded",
|
|
13778
|
+
processing_state: "recorded"
|
|
13779
|
+
});
|
|
13780
|
+
}
|
|
13781
|
+
if (!privateLessonHealthReportingEnabled()) {
|
|
13782
|
+
draft = await updatePrivateLessonDraft(draft.draft_id, (current) => ({
|
|
13783
|
+
...current,
|
|
13784
|
+
hold_telemetry_status: "disabled",
|
|
13785
|
+
next_retry_at: null,
|
|
13786
|
+
last_error_code: "health_reporting_disabled"
|
|
13787
|
+
}));
|
|
13788
|
+
return privateLessonDraftActionResult(draft, {
|
|
13789
|
+
status: "not_submitted",
|
|
13790
|
+
action_required: null
|
|
13791
|
+
});
|
|
13792
|
+
}
|
|
13793
|
+
let verifiedPolicy;
|
|
13794
|
+
let currentBinding;
|
|
13795
|
+
try {
|
|
13796
|
+
verifiedPolicy = await fetchVerifiedPrivateLessonPolicy(access);
|
|
13797
|
+
currentBinding = bindingForPrivateLessonPolicy(verifiedPolicy.policy);
|
|
13798
|
+
await rememberPrivateLessonPolicyFloor(currentBinding);
|
|
13799
|
+
} catch (error) {
|
|
13800
|
+
draft = await persistPrivateLessonHoldFailure(draft, error);
|
|
13801
|
+
return privateLessonDraftActionResult(draft, {
|
|
13802
|
+
status: "not_submitted",
|
|
13803
|
+
action_required: draft.hold_telemetry_status === "awaiting_authorization" ? "Authorize the exact submit_private_lesson_candidate action, then retry." : null
|
|
13804
|
+
});
|
|
13805
|
+
}
|
|
13806
|
+
if (!verifiedPolicy.policy.payload.enabled || verifiedPolicy.policy.payload.kill_switch) {
|
|
13807
|
+
draft = await updatePrivateLessonDraft(draft.draft_id, (current) => ({
|
|
13808
|
+
...current,
|
|
13809
|
+
policy: currentBinding,
|
|
13810
|
+
hold_telemetry_status: "disabled",
|
|
13811
|
+
next_retry_at: null,
|
|
13812
|
+
last_error_code: "private_lesson_automation_disabled"
|
|
13813
|
+
}));
|
|
13814
|
+
return privateLessonDraftActionResult(draft, {
|
|
13815
|
+
status: "not_submitted",
|
|
13816
|
+
action_required: null
|
|
13817
|
+
});
|
|
13818
|
+
}
|
|
13819
|
+
const policyConflict = privateLessonPolicyBindingConflict(
|
|
13820
|
+
draft.policy,
|
|
13821
|
+
currentBinding,
|
|
13822
|
+
allowPolicyRefresh
|
|
13823
|
+
);
|
|
13824
|
+
if (policyConflict) {
|
|
13825
|
+
draft = await persistPrivateLessonHoldFailure(
|
|
13826
|
+
draft,
|
|
13827
|
+
new PrivateLessonPolicySafetyError(policyConflict.message),
|
|
13828
|
+
policyConflict.code
|
|
13829
|
+
);
|
|
13830
|
+
return privateLessonDraftActionResult(draft, {
|
|
13831
|
+
status: "not_submitted",
|
|
13832
|
+
action_required: policyConflict.action_required
|
|
13833
|
+
});
|
|
13834
|
+
}
|
|
13835
|
+
if (!draft.policy) {
|
|
13836
|
+
draft = await updatePrivateLessonDraft(draft.draft_id, (current) => ({
|
|
13837
|
+
...current,
|
|
13838
|
+
policy: current.policy ?? currentBinding
|
|
13839
|
+
}));
|
|
13840
|
+
}
|
|
13841
|
+
const submissionBindings = draft.policy && draft.policy.policy_digest !== currentBinding.policy_digest && allowPolicyRefresh ? [draft.policy, currentBinding] : [currentBinding];
|
|
13842
|
+
for (const submissionBinding of submissionBindings) {
|
|
13843
|
+
try {
|
|
13844
|
+
const submitted = await submitPrivateLessonHoldTelemetryWithBinding(
|
|
13845
|
+
draft,
|
|
13846
|
+
access,
|
|
13847
|
+
submissionBinding
|
|
13848
|
+
);
|
|
13849
|
+
const receiptPolicy = [
|
|
13850
|
+
submissionBinding,
|
|
13851
|
+
currentBinding,
|
|
13852
|
+
draft.policy
|
|
13853
|
+
].find(
|
|
13854
|
+
(binding) => binding?.policy_digest === submitted.receipt.payload.policy_digest
|
|
13855
|
+
);
|
|
13856
|
+
if (submitted.body.event_digest !== submitted.receipt.payload.event_digest || submitted.body.policy_digest !== submitted.receipt.payload.policy_digest || submitted.receipt.payload.event_digest !== submitted.eventDigest || submitted.receipt.payload.contract_version !== draft.candidate.contract_version || submitted.receipt.payload.redaction_version !== draft.redaction_version || submitted.receipt.payload.redactor_digest !== draft.redactor_digest || !receiptPolicy || submitted.receipt.payload.organization_audience_hash !== receiptPolicy.organization_audience_hash) {
|
|
13857
|
+
throw new PrivateLessonPolicySafetyError(
|
|
13858
|
+
"Private lesson hold receipt does not match the submitted event."
|
|
13859
|
+
);
|
|
13860
|
+
}
|
|
13861
|
+
draft = await updatePrivateLessonDraft(draft.draft_id, (current) => ({
|
|
13862
|
+
...current,
|
|
13863
|
+
attempts: Math.min(1e3, current.attempts + 1),
|
|
13864
|
+
next_retry_at: null,
|
|
13865
|
+
last_error_code: null,
|
|
13866
|
+
hold_telemetry_status: "submitted",
|
|
13867
|
+
hold_event_digest: submitted.eventDigest,
|
|
13868
|
+
hold_receipt_digest: hashValue(canonicalJson(submitted.receipt)),
|
|
13869
|
+
hold_reported_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13870
|
+
policy: receiptPolicy
|
|
13871
|
+
}));
|
|
13872
|
+
return {
|
|
13873
|
+
...privateLessonDraftActionResult(draft, {
|
|
13874
|
+
status: submitted.status
|
|
13875
|
+
}),
|
|
13876
|
+
receipt: submitted.receipt,
|
|
13877
|
+
processing_state: submitted.processingState
|
|
13878
|
+
};
|
|
13879
|
+
} catch (error) {
|
|
13880
|
+
const isRetainedPolicyAttempt = submissionBinding.policy_digest !== currentBinding.policy_digest;
|
|
13881
|
+
if (isRetainedPolicyAttempt && privateLessonSubmissionRequiresPolicyRefresh(error)) {
|
|
13882
|
+
continue;
|
|
13883
|
+
}
|
|
13884
|
+
draft = await persistPrivateLessonHoldFailure(draft, error);
|
|
13885
|
+
return privateLessonDraftActionResult(draft, {
|
|
13886
|
+
status: "not_submitted",
|
|
13887
|
+
action_required: draft.hold_telemetry_status === "awaiting_authorization" ? "Authorize the exact submit_private_lesson_candidate action, then retry." : null
|
|
13888
|
+
});
|
|
13889
|
+
}
|
|
13890
|
+
}
|
|
13891
|
+
throw new PrivateLessonPolicySafetyError(
|
|
13892
|
+
"Private lesson hold telemetry could not resolve an active policy binding."
|
|
13893
|
+
);
|
|
13894
|
+
}
|
|
13895
|
+
async function submitPrivateLessonHoldTelemetryWithBinding(draft, access, policyBinding) {
|
|
13896
|
+
const event = privateLessonHoldEventPayloadSchema.parse({
|
|
13897
|
+
version: PRIVATE_LESSON_HOLD_EVENT_VERSION,
|
|
13898
|
+
record_type: "held_safety_event",
|
|
13899
|
+
event_type: draft.candidate.event_type,
|
|
13900
|
+
held_categories: draft.held_reasons,
|
|
13901
|
+
redaction_counts: Object.fromEntries(
|
|
13902
|
+
draft.held_reasons.flatMap((category) => {
|
|
13903
|
+
const count = draft.candidate.redaction_counts[category];
|
|
13904
|
+
return count ? [[category, count]] : [];
|
|
13905
|
+
})
|
|
13906
|
+
),
|
|
13907
|
+
contract_version: PRIVATE_LESSON_CONTRACT_VERSION,
|
|
13908
|
+
redaction_version: draft.redaction_version,
|
|
13909
|
+
redactor_digest: draft.redactor_digest,
|
|
13910
|
+
policy_digest: policyBinding.policy_digest
|
|
13911
|
+
});
|
|
13912
|
+
const eventDigest = privateLessonHoldEventDigest(event);
|
|
13913
|
+
if (canonicalPrivateLessonHoldEvent(event).length > 4 * 1024) {
|
|
13914
|
+
throw new PrivateLessonPolicySafetyError(
|
|
13915
|
+
"Private lesson hold telemetry exceeds its content-free size bound."
|
|
13916
|
+
);
|
|
13917
|
+
}
|
|
13918
|
+
const identity = await ensureSigningIdentity();
|
|
13919
|
+
const challengeResult = await callRemembrance(
|
|
13920
|
+
mustFindTool("request_attestation_challenge"),
|
|
13921
|
+
{
|
|
13922
|
+
provider: identity.provider,
|
|
13923
|
+
source_type: "private_lesson",
|
|
13924
|
+
agent_id: identity.subject,
|
|
13925
|
+
evidence_hash: eventDigest,
|
|
13926
|
+
private_lesson: {
|
|
13927
|
+
key_id: identity.key_id,
|
|
13928
|
+
record_type: "held_safety_event",
|
|
13929
|
+
organization_audience_hash: policyBinding.organization_audience_hash,
|
|
13930
|
+
event_digest: eventDigest,
|
|
13931
|
+
policy_digest: policyBinding.policy_digest,
|
|
13932
|
+
redactor_digest: draft.redactor_digest
|
|
13933
|
+
}
|
|
13934
|
+
},
|
|
13935
|
+
{ access, skipEconomicsSession: true }
|
|
13936
|
+
);
|
|
13937
|
+
const challenge = requirePrivateLessonRemoteBody(
|
|
13938
|
+
challengeResult,
|
|
13939
|
+
"challenge"
|
|
13940
|
+
);
|
|
13941
|
+
const submission = {
|
|
13942
|
+
record_type: "held_safety_event",
|
|
13943
|
+
event,
|
|
13944
|
+
event_digest: eventDigest,
|
|
13945
|
+
idempotency_key: privateLessonHoldIdempotencyKey(draft, eventDigest),
|
|
13946
|
+
attestation: {
|
|
13947
|
+
version: "private-lesson-attestation-v1",
|
|
13948
|
+
purpose: "remembrance-private-lesson-submission",
|
|
13949
|
+
provider: identity.provider,
|
|
13950
|
+
challenge_id: stringField(challenge, "challenge_id"),
|
|
13951
|
+
key_id: identity.key_id,
|
|
13952
|
+
algorithm: "ed25519",
|
|
13953
|
+
nonce: stringField(challenge, "nonce"),
|
|
13954
|
+
audience: "remembrance-private-lesson",
|
|
13955
|
+
record_type: "held_safety_event",
|
|
13956
|
+
organization_audience_hash: policyBinding.organization_audience_hash,
|
|
13957
|
+
event_digest: eventDigest,
|
|
13958
|
+
policy_digest: policyBinding.policy_digest,
|
|
13959
|
+
redactor_digest: draft.redactor_digest,
|
|
13960
|
+
issued_at: stringField(challenge, "issued_at"),
|
|
13961
|
+
expires_at: stringField(challenge, "expires_at"),
|
|
13962
|
+
signature: signPayload(
|
|
13963
|
+
null,
|
|
13964
|
+
Buffer.from(stringField(challenge, "signing_payload_canonical")),
|
|
13965
|
+
createPrivateKey2(identity.private_key)
|
|
13966
|
+
).toString("base64url")
|
|
13967
|
+
}
|
|
13968
|
+
};
|
|
13969
|
+
const result = await callRemembrance(
|
|
13970
|
+
mustFindTool("submit_private_lesson_candidate"),
|
|
13971
|
+
submission,
|
|
13972
|
+
{ access }
|
|
13973
|
+
);
|
|
13974
|
+
if (privateLessonPolicyRefreshRequiredResponse(result)) {
|
|
13975
|
+
throw new PrivateLessonPolicyRefreshRequiredError();
|
|
13976
|
+
}
|
|
13977
|
+
const body = requirePrivateLessonRemoteBody(result, "submission");
|
|
13978
|
+
const status = String(body.status ?? "");
|
|
13979
|
+
const processingState = String(body.processing_state ?? "");
|
|
13980
|
+
if (status !== "accepted_hold_telemetry" && status !== "duplicate" || processingState !== "recorded" && processingState !== "already_recorded") {
|
|
13981
|
+
throw new PrivateLessonPolicySafetyError(
|
|
13982
|
+
"Private lesson hold submission returned an unsupported state."
|
|
13983
|
+
);
|
|
13984
|
+
}
|
|
13985
|
+
const receipt = await verifyPrivateLessonEnvelopeWithKeyRefresh(
|
|
13986
|
+
body.receipt,
|
|
13987
|
+
"hold_receipt",
|
|
13988
|
+
access
|
|
13989
|
+
);
|
|
13990
|
+
return { body, eventDigest, processingState, receipt, status };
|
|
13991
|
+
}
|
|
13992
|
+
async function fetchVerifiedPrivateLessonPolicy(access) {
|
|
13993
|
+
const result = await callRemembrance(
|
|
13994
|
+
mustFindTool("get_private_lesson_policy"),
|
|
13995
|
+
{},
|
|
13996
|
+
{ access, skipEconomicsSession: true }
|
|
13997
|
+
);
|
|
13998
|
+
const body = requirePrivateLessonRemoteBody(result, "policy");
|
|
13999
|
+
const verified = await verifyPrivateLessonEnvelopeWithKeyRefresh(
|
|
14000
|
+
body.policy,
|
|
14001
|
+
"policy",
|
|
14002
|
+
access
|
|
14003
|
+
);
|
|
14004
|
+
const digest = privateLessonPolicyDigest(verified.payload);
|
|
14005
|
+
if (body.policy_digest !== digest) {
|
|
14006
|
+
throw new PrivateLessonPolicySafetyError(
|
|
14007
|
+
"Private lesson policy digest does not match its signed payload."
|
|
14008
|
+
);
|
|
14009
|
+
}
|
|
14010
|
+
return { policy: verified, body };
|
|
14011
|
+
}
|
|
14012
|
+
async function verifyPrivateLessonEnvelopeWithKeyRefresh(envelope, kind, access) {
|
|
14013
|
+
let keySet;
|
|
14014
|
+
try {
|
|
14015
|
+
keySet = await fetchValueProofKeySet(false, access);
|
|
14016
|
+
} catch {
|
|
14017
|
+
throw new PrivateLessonRemoteError(
|
|
14018
|
+
"Private lesson verification keys are temporarily unavailable.",
|
|
14019
|
+
0,
|
|
14020
|
+
"verification_keys_unavailable",
|
|
14021
|
+
true
|
|
14022
|
+
);
|
|
14023
|
+
}
|
|
14024
|
+
try {
|
|
14025
|
+
return verifyPrivateLessonEnvelope(envelope, kind, keySet);
|
|
14026
|
+
} catch (error) {
|
|
14027
|
+
rethrowPrivateLessonProfileVerificationError(error, kind);
|
|
14028
|
+
let refreshedKeySet;
|
|
14029
|
+
try {
|
|
14030
|
+
refreshedKeySet = await fetchValueProofKeySet(true, access);
|
|
14031
|
+
} catch {
|
|
14032
|
+
throw new PrivateLessonRemoteError(
|
|
14033
|
+
"Private lesson verification keys are temporarily unavailable.",
|
|
14034
|
+
0,
|
|
14035
|
+
"verification_keys_unavailable",
|
|
14036
|
+
true
|
|
14037
|
+
);
|
|
14038
|
+
}
|
|
14039
|
+
try {
|
|
14040
|
+
return verifyPrivateLessonEnvelope(envelope, kind, refreshedKeySet);
|
|
14041
|
+
} catch (error2) {
|
|
14042
|
+
rethrowPrivateLessonProfileVerificationError(error2, kind);
|
|
14043
|
+
throw new PrivateLessonPolicySafetyError(
|
|
14044
|
+
`Private lesson ${kind} signature verification failed.`
|
|
14045
|
+
);
|
|
14046
|
+
}
|
|
14047
|
+
}
|
|
14048
|
+
}
|
|
14049
|
+
function rethrowPrivateLessonProfileVerificationError(error, kind) {
|
|
14050
|
+
if (error instanceof PrivateLessonRedactionProfileError) {
|
|
14051
|
+
throw new PrivateLessonPolicySafetyError(
|
|
14052
|
+
`Private lesson ${kind} uses an unsupported redactor profile (${error.code}).`
|
|
14053
|
+
);
|
|
14054
|
+
}
|
|
14055
|
+
}
|
|
14056
|
+
function verifyPrivateLessonEnvelope(envelope, kind, keySet) {
|
|
14057
|
+
if (kind === "policy") {
|
|
14058
|
+
return verifyPrivateLessonSignedEnvelope({ envelope, kind, keySet });
|
|
14059
|
+
}
|
|
14060
|
+
if (kind === "receipt") {
|
|
14061
|
+
return verifyPrivateLessonSignedEnvelope({ envelope, kind, keySet });
|
|
14062
|
+
}
|
|
14063
|
+
return verifyPrivateLessonSignedEnvelope({ envelope, kind, keySet });
|
|
14064
|
+
}
|
|
14065
|
+
function requirePrivateLessonRemoteBody(result, operation) {
|
|
14066
|
+
const response = isRecord5(result) ? result : {};
|
|
14067
|
+
const status = typeof response.status === "number" ? response.status : 0;
|
|
14068
|
+
if (response.ok !== true || !isRecord5(response.body)) {
|
|
14069
|
+
const retryable = status === 0 || status === 408 || status === 429 || status >= 500;
|
|
14070
|
+
const code = status === 401 || status === 403 ? "authorization_required" : retryable ? "service_unavailable" : `${operation}_rejected`;
|
|
14071
|
+
throw new PrivateLessonRemoteError(
|
|
14072
|
+
`Private lesson ${operation} is unavailable${status ? ` (${status})` : ""}.`,
|
|
14073
|
+
status,
|
|
14074
|
+
code,
|
|
14075
|
+
retryable
|
|
14076
|
+
);
|
|
14077
|
+
}
|
|
14078
|
+
return response.body;
|
|
14079
|
+
}
|
|
14080
|
+
function bindingForPrivateLessonPolicy(policy) {
|
|
14081
|
+
return {
|
|
14082
|
+
policy_digest: privateLessonPolicyDigest(policy.payload),
|
|
14083
|
+
policy_sequence: policy.payload.policy_sequence,
|
|
14084
|
+
organization_audience_hash: policy.payload.organization_audience_hash,
|
|
14085
|
+
expires_at: policy.payload.expires_at,
|
|
14086
|
+
enabled_tiers: policy.payload.enabled_tiers,
|
|
14087
|
+
redaction_version: policy.payload.redaction_version,
|
|
14088
|
+
redactor_digest: policy.payload.redactor_digest
|
|
14089
|
+
};
|
|
14090
|
+
}
|
|
14091
|
+
function privateLessonPolicyAllows(policy, tier) {
|
|
14092
|
+
return policy.payload.enabled && !policy.payload.kill_switch && policy.payload.encryption_ready && policy.payload.public_fallback === false && policy.payload.redaction_version === PRIVATE_LESSON_REDACTION_VERSION && policy.payload.redactor_digest === privateLessonRedactorDigest() && policy.payload.enabled_tiers.includes(tier);
|
|
14093
|
+
}
|
|
14094
|
+
function privateLessonHealthReportingEnabled(env = process.env) {
|
|
14095
|
+
return !/^(0|false|no)$/i.test(env.REMEMBRANCE_HEALTH_REPORTING ?? "");
|
|
14096
|
+
}
|
|
14097
|
+
function privateLessonHoldIdempotencyKey(draft, eventDigest) {
|
|
14098
|
+
return `private-lesson-hold-${hashValue(
|
|
14099
|
+
canonicalJson({ draft_id: draft.draft_id, event_digest: eventDigest })
|
|
14100
|
+
).replace(/^sha256:/, "")}`;
|
|
14101
|
+
}
|
|
14102
|
+
function privateLessonPolicyRefreshRequiredResponse(result) {
|
|
14103
|
+
if (!isRecord5(result) || result.status !== 409 || !isRecord5(result.body)) {
|
|
14104
|
+
return false;
|
|
14105
|
+
}
|
|
14106
|
+
return result.body.code === "policy_changed" || result.body.error === "Private lesson policy changed; refresh policy before retrying.";
|
|
14107
|
+
}
|
|
14108
|
+
function privateLessonSubmissionRequiresPolicyRefresh(error) {
|
|
14109
|
+
return error instanceof PrivateLessonPolicyRefreshRequiredError;
|
|
14110
|
+
}
|
|
14111
|
+
function privateLessonPolicyBindingConflict(stored, current, allowPolicyRefresh) {
|
|
14112
|
+
if (!stored) return null;
|
|
14113
|
+
if (stored.organization_audience_hash !== current.organization_audience_hash) {
|
|
14114
|
+
return {
|
|
14115
|
+
code: "policy_audience_changed",
|
|
14116
|
+
message: "Private lesson policy audience changed after preparation.",
|
|
14117
|
+
action_required: "Keep this draft retained and prepare a new draft for the active organization."
|
|
14118
|
+
};
|
|
14119
|
+
}
|
|
14120
|
+
if (current.policy_sequence < stored.policy_sequence) {
|
|
14121
|
+
return {
|
|
14122
|
+
code: "policy_downgrade",
|
|
14123
|
+
message: "Private lesson policy sequence moved backward.",
|
|
14124
|
+
action_required: null
|
|
14125
|
+
};
|
|
14126
|
+
}
|
|
14127
|
+
if (current.policy_sequence === stored.policy_sequence && current.policy_digest !== stored.policy_digest) {
|
|
14128
|
+
return {
|
|
14129
|
+
code: "policy_sequence_conflict",
|
|
14130
|
+
message: "Private lesson policy content conflicts at the same sequence.",
|
|
14131
|
+
action_required: null
|
|
14132
|
+
};
|
|
14133
|
+
}
|
|
14134
|
+
if (current.policy_digest !== stored.policy_digest && !allowPolicyRefresh) {
|
|
14135
|
+
return {
|
|
14136
|
+
code: "policy_changed",
|
|
14137
|
+
message: "Organization private lesson policy changed after preparation.",
|
|
14138
|
+
action_required: "Explicitly retry the retained draft under the current policy."
|
|
14139
|
+
};
|
|
14140
|
+
}
|
|
14141
|
+
return null;
|
|
14142
|
+
}
|
|
14143
|
+
async function persistPrivateLessonFailure(draft, error, explicitCode) {
|
|
14144
|
+
return updatePrivateLessonDraft(
|
|
14145
|
+
draft.draft_id,
|
|
14146
|
+
(current) => current.state === "submitted" || current.state === "superseded_redactor" ? current : {
|
|
14147
|
+
...current,
|
|
14148
|
+
...privateLessonFailureTransition(current, error, explicitCode)
|
|
14149
|
+
}
|
|
14150
|
+
);
|
|
14151
|
+
}
|
|
14152
|
+
async function persistPrivateLessonHoldFailure(draft, error, explicitCode) {
|
|
14153
|
+
return updatePrivateLessonDraft(draft.draft_id, (current) => {
|
|
14154
|
+
if (current.state === "superseded_redactor" || current.hold_telemetry_status === "submitted") {
|
|
14155
|
+
return current;
|
|
14156
|
+
}
|
|
14157
|
+
const attempts = Math.min(1e3, current.attempts + 1);
|
|
14158
|
+
if (error instanceof PrivateLessonRemoteError) {
|
|
14159
|
+
if (error.status === 401 || error.status === 403) {
|
|
14160
|
+
return {
|
|
14161
|
+
...current,
|
|
14162
|
+
attempts,
|
|
14163
|
+
next_retry_at: null,
|
|
14164
|
+
last_error_code: explicitCode ?? error.code,
|
|
14165
|
+
hold_telemetry_status: "awaiting_authorization"
|
|
14166
|
+
};
|
|
14167
|
+
}
|
|
14168
|
+
if (error.retryable) {
|
|
14169
|
+
return {
|
|
14170
|
+
...current,
|
|
14171
|
+
attempts,
|
|
14172
|
+
next_retry_at: new Date(
|
|
14173
|
+
Date.now() + privateLessonRetryDelayMs(attempts)
|
|
14174
|
+
).toISOString(),
|
|
14175
|
+
last_error_code: explicitCode ?? error.code,
|
|
14176
|
+
hold_telemetry_status: "retry_scheduled"
|
|
14177
|
+
};
|
|
14178
|
+
}
|
|
14179
|
+
}
|
|
14180
|
+
return {
|
|
14181
|
+
...current,
|
|
14182
|
+
attempts,
|
|
14183
|
+
next_retry_at: null,
|
|
14184
|
+
last_error_code: explicitCode ?? "validation_or_policy_hold",
|
|
14185
|
+
hold_telemetry_status: "disabled"
|
|
14186
|
+
};
|
|
14187
|
+
});
|
|
14188
|
+
}
|
|
14189
|
+
function privateLessonFailureTransition(draft, error, explicitCode) {
|
|
14190
|
+
const attempts = Math.min(1e3, draft.attempts + 1);
|
|
14191
|
+
if (error instanceof PrivateLessonRemoteError) {
|
|
14192
|
+
if (error.status === 401 || error.status === 403) {
|
|
14193
|
+
return {
|
|
14194
|
+
state: "awaiting_authorization",
|
|
14195
|
+
attempts,
|
|
14196
|
+
next_retry_at: null,
|
|
14197
|
+
last_error_code: explicitCode ?? error.code
|
|
14198
|
+
};
|
|
14199
|
+
}
|
|
14200
|
+
if (error.retryable) {
|
|
14201
|
+
return {
|
|
14202
|
+
state: "retry_scheduled",
|
|
14203
|
+
attempts,
|
|
14204
|
+
next_retry_at: new Date(
|
|
14205
|
+
Date.now() + privateLessonRetryDelayMs(attempts)
|
|
14206
|
+
).toISOString(),
|
|
14207
|
+
last_error_code: explicitCode ?? error.code
|
|
14208
|
+
};
|
|
14209
|
+
}
|
|
14210
|
+
}
|
|
14211
|
+
return {
|
|
14212
|
+
state: "held_safety",
|
|
14213
|
+
attempts,
|
|
14214
|
+
next_retry_at: null,
|
|
14215
|
+
last_error_code: explicitCode ?? "validation_or_policy_hold"
|
|
14216
|
+
};
|
|
14217
|
+
}
|
|
14218
|
+
function privateLessonRetryDelayMs(attempts) {
|
|
14219
|
+
return Math.min(6 * 60 * 60 * 1e3, 5e3 * 2 ** Math.min(attempts - 1, 12));
|
|
14220
|
+
}
|
|
14221
|
+
function privateLessonDraftActionResult(draft, extra = {}) {
|
|
14222
|
+
return {
|
|
14223
|
+
draft_id: draft.draft_id,
|
|
14224
|
+
state: draft.state,
|
|
14225
|
+
payload_digest: draft.payload_digest,
|
|
14226
|
+
redaction_version: draft.redaction_version,
|
|
14227
|
+
redactor_digest: draft.redactor_digest,
|
|
14228
|
+
redaction_counts: draft.candidate.redaction_counts,
|
|
14229
|
+
held_reasons: draft.held_reasons,
|
|
14230
|
+
attempts: draft.attempts,
|
|
14231
|
+
next_retry_at: draft.next_retry_at,
|
|
14232
|
+
last_error_code: draft.last_error_code,
|
|
14233
|
+
terminal_reason: draft.terminal_reason,
|
|
14234
|
+
hold_telemetry_status: draft.hold_telemetry_status,
|
|
14235
|
+
hold_event_digest: draft.hold_event_digest,
|
|
14236
|
+
hold_receipt_digest: draft.hold_receipt_digest,
|
|
14237
|
+
hold_reported_at: draft.hold_reported_at,
|
|
14238
|
+
retained_locally: draft.state !== "submitted",
|
|
14239
|
+
...extra
|
|
14240
|
+
};
|
|
14241
|
+
}
|
|
14242
|
+
function publicOutboxSummary(summary) {
|
|
14243
|
+
const { directory: _directory, ...safe } = summary;
|
|
14244
|
+
return safe;
|
|
14245
|
+
}
|
|
14246
|
+
function stringField(record2, key) {
|
|
14247
|
+
const value = record2[key];
|
|
14248
|
+
if (typeof value !== "string" || !value) {
|
|
14249
|
+
throw new PrivateLessonPolicySafetyError(
|
|
14250
|
+
`Private lesson response omitted ${key}.`
|
|
14251
|
+
);
|
|
14252
|
+
}
|
|
14253
|
+
return value;
|
|
14254
|
+
}
|
|
11480
14255
|
async function submitRemembrance(payload, access) {
|
|
11481
14256
|
const { verified_attestation: verifiedAttestation, ...request } = payload;
|
|
11482
14257
|
if (!verifiedAttestation) {
|
|
@@ -11611,7 +14386,7 @@ async function signedRemembranceAttestation(remembrancePayload, skillSlug, acces
|
|
|
11611
14386
|
}
|
|
11612
14387
|
async function bootstrapAgentIdentity(args, access = resolveApiAccess()) {
|
|
11613
14388
|
const keyPath = identityPath(args.key_path);
|
|
11614
|
-
let reusedExistingIdentity = !args.force_rotate &&
|
|
14389
|
+
let reusedExistingIdentity = !args.force_rotate && existsSync5(keyPath);
|
|
11615
14390
|
let identity = reusedExistingIdentity ? await readIdentity(keyPath) : null;
|
|
11616
14391
|
if (!identity) {
|
|
11617
14392
|
const created = await createAndPersistIdentity(args, keyPath);
|
|
@@ -11725,7 +14500,7 @@ async function createAndPersistIdentity(args, keyPath) {
|
|
|
11725
14500
|
private_key: String(privateKey.export({ type: "pkcs8", format: "pem" })),
|
|
11726
14501
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
11727
14502
|
};
|
|
11728
|
-
await
|
|
14503
|
+
await mkdir3(dirname3(keyPath), { recursive: true, mode: 448 });
|
|
11729
14504
|
try {
|
|
11730
14505
|
await writeFile2(keyPath, `${JSON.stringify(identity, null, 2)}
|
|
11731
14506
|
`, {
|
|
@@ -11744,7 +14519,7 @@ async function createAndPersistIdentity(args, keyPath) {
|
|
|
11744
14519
|
}
|
|
11745
14520
|
}
|
|
11746
14521
|
async function readIdentity(path = identityPath()) {
|
|
11747
|
-
if (!
|
|
14522
|
+
if (!existsSync5(path)) {
|
|
11748
14523
|
return null;
|
|
11749
14524
|
}
|
|
11750
14525
|
try {
|
|
@@ -11841,7 +14616,7 @@ async function callRemembrance(definition, rawArguments, options = {}) {
|
|
|
11841
14616
|
if (principalSession) {
|
|
11842
14617
|
headers["x-remembrance-principal-session"] = principalSession;
|
|
11843
14618
|
} else if (definition.name === "query_skills" || definition.name === "invoke_skill") {
|
|
11844
|
-
|
|
14619
|
+
warmPrincipalSession(access);
|
|
11845
14620
|
}
|
|
11846
14621
|
}
|
|
11847
14622
|
const endpoint = endpointFor(definition, payload);
|
|
@@ -11885,7 +14660,7 @@ async function callRemembrance(definition, rawArguments, options = {}) {
|
|
|
11885
14660
|
);
|
|
11886
14661
|
if (sentPrincipalSession && refreshSignaled && !authenticationFailed) {
|
|
11887
14662
|
cachedPrincipalSession = null;
|
|
11888
|
-
|
|
14663
|
+
warmPrincipalSession(access);
|
|
11889
14664
|
} else if (sentPrincipalSession && authenticationFailed) {
|
|
11890
14665
|
cachedPrincipalSession = null;
|
|
11891
14666
|
const refreshed = await ensurePrincipalSessionToken(access).catch(
|
|
@@ -11941,7 +14716,7 @@ async function localProjectKey() {
|
|
|
11941
14716
|
}
|
|
11942
14717
|
function principalSessionAuthenticationFailed(response, body) {
|
|
11943
14718
|
if (response.status !== 401 && response.status !== 403) return false;
|
|
11944
|
-
if (!
|
|
14719
|
+
if (!isRecord5(body) || typeof body.error !== "string") return false;
|
|
11945
14720
|
return /principal session|economics session|installation session|agent principal|installation principal/i.test(
|
|
11946
14721
|
body.error
|
|
11947
14722
|
);
|
|
@@ -11959,10 +14734,20 @@ async function runLocalConnectionDoctor(options, execution = "mcp_tool", access
|
|
|
11959
14734
|
error: localConfigurationError
|
|
11960
14735
|
}) : callDoctorReadProbe(connectionDefinition, {}, access);
|
|
11961
14736
|
const catalogPromise = options.active_read_probe && !localConfigurationError ? callDoctorReadProbe(mustFindTool("list_skills"), { limit: 1 }, access) : Promise.resolve(null);
|
|
11962
|
-
const
|
|
14737
|
+
const policyPromise = access.credential.apiKey ? fetchVerifiedPrivateLessonPolicy(access).then((value) => ({ ok: true, value })).catch((error) => ({ ok: false, error })) : Promise.resolve(null);
|
|
14738
|
+
const outboxPromise = inspectPrivateLessonOutboxHealth().then((value) => ({ ok: true, value })).catch((error) => ({ ok: false, error }));
|
|
14739
|
+
const [
|
|
14740
|
+
connectionResult,
|
|
14741
|
+
catalogResult,
|
|
14742
|
+
clientUpdate,
|
|
14743
|
+
policyResult,
|
|
14744
|
+
outboxResult
|
|
14745
|
+
] = await Promise.all([
|
|
11963
14746
|
connectionPromise,
|
|
11964
14747
|
catalogPromise,
|
|
11965
|
-
currentClientUpdate(access)
|
|
14748
|
+
currentClientUpdate(access),
|
|
14749
|
+
policyPromise,
|
|
14750
|
+
outboxPromise
|
|
11966
14751
|
]);
|
|
11967
14752
|
const connectionStatus = localConnectionStatus(connectionResult, {
|
|
11968
14753
|
apiBase: access.configuration.baseUrl,
|
|
@@ -11975,15 +14760,15 @@ async function runLocalConnectionDoctor(options, execution = "mcp_tool", access
|
|
|
11975
14760
|
item_count: null
|
|
11976
14761
|
};
|
|
11977
14762
|
if (options.active_read_probe && !localConfigurationError) {
|
|
11978
|
-
const resultRecord =
|
|
11979
|
-
const body =
|
|
14763
|
+
const resultRecord = isRecord5(catalogResult) ? catalogResult : {};
|
|
14764
|
+
const body = isRecord5(resultRecord.body) ? resultRecord.body : {};
|
|
11980
14765
|
const skills = Array.isArray(body.skills) ? body.skills : null;
|
|
11981
14766
|
activeReadProbe.succeeded = resultRecord.ok === true && skills !== null;
|
|
11982
14767
|
activeReadProbe.item_count = skills ? skills.length : null;
|
|
11983
14768
|
} else if (options.active_read_probe) {
|
|
11984
14769
|
activeReadProbe.succeeded = false;
|
|
11985
14770
|
}
|
|
11986
|
-
|
|
14771
|
+
const report = buildConnectionDoctorReport({
|
|
11987
14772
|
connection_status: connectionStatus,
|
|
11988
14773
|
transport: execution === "standalone_cli" ? "standalone_cli" : "local_stdio_mcp",
|
|
11989
14774
|
host_registration_observed: execution === "mcp_tool",
|
|
@@ -11991,6 +14776,90 @@ async function runLocalConnectionDoctor(options, execution = "mcp_tool", access
|
|
|
11991
14776
|
check_organization_write_authorization: options.check_organization_write_authorization,
|
|
11992
14777
|
client_update: clientUpdate
|
|
11993
14778
|
});
|
|
14779
|
+
const privateLessonAutomation = privateLessonDoctorSummary({
|
|
14780
|
+
report,
|
|
14781
|
+
policyResult,
|
|
14782
|
+
outboxResult
|
|
14783
|
+
});
|
|
14784
|
+
return {
|
|
14785
|
+
...report,
|
|
14786
|
+
status: report.status === "healthy" && privateLessonAutomation.status !== "ready" && privateLessonAutomation.status !== "not_applicable" ? "attention" : report.status,
|
|
14787
|
+
private_lesson_automation: privateLessonAutomation
|
|
14788
|
+
};
|
|
14789
|
+
}
|
|
14790
|
+
function privateLessonDoctorSummary(args) {
|
|
14791
|
+
const organization = args.report.scope === "organization";
|
|
14792
|
+
const policy = !organization ? {
|
|
14793
|
+
status: "not_applicable",
|
|
14794
|
+
contract_version: null,
|
|
14795
|
+
redaction_version: null,
|
|
14796
|
+
redactor_digest: null,
|
|
14797
|
+
policy_sequence: null,
|
|
14798
|
+
enabled_tiers: [],
|
|
14799
|
+
encryption_ready: null,
|
|
14800
|
+
public_fallback: null
|
|
14801
|
+
} : args.policyResult?.ok ? {
|
|
14802
|
+
status: privateLessonPolicyAllows(
|
|
14803
|
+
args.policyResult.value.policy,
|
|
14804
|
+
"structured_metadata"
|
|
14805
|
+
) || privateLessonPolicyAllows(
|
|
14806
|
+
args.policyResult.value.policy,
|
|
14807
|
+
"bounded_redacted_prose"
|
|
14808
|
+
) ? "ready" : "disabled",
|
|
14809
|
+
contract_version: args.policyResult.value.policy.payload.contract_version,
|
|
14810
|
+
redaction_version: args.policyResult.value.policy.payload.redaction_version,
|
|
14811
|
+
redactor_digest: privateLessonRedactorDigest(),
|
|
14812
|
+
policy_sequence: args.policyResult.value.policy.payload.policy_sequence,
|
|
14813
|
+
enabled_tiers: args.policyResult.value.policy.payload.enabled_tiers,
|
|
14814
|
+
encryption_ready: args.policyResult.value.policy.payload.encryption_ready,
|
|
14815
|
+
public_fallback: args.policyResult.value.policy.payload.public_fallback
|
|
14816
|
+
} : {
|
|
14817
|
+
status: args.policyResult?.error instanceof PrivateLessonRemoteError && (args.policyResult.error.status === 401 || args.policyResult.error.status === 403) ? "authorization_required" : args.policyResult?.error instanceof PrivateLessonPolicySafetyError ? "invalid" : "unavailable",
|
|
14818
|
+
contract_version: PRIVATE_LESSON_CONTRACT_VERSION,
|
|
14819
|
+
redaction_version: PRIVATE_LESSON_REDACTION_VERSION,
|
|
14820
|
+
redactor_digest: privateLessonRedactorDigest(),
|
|
14821
|
+
policy_sequence: null,
|
|
14822
|
+
enabled_tiers: [],
|
|
14823
|
+
encryption_ready: null,
|
|
14824
|
+
public_fallback: null
|
|
14825
|
+
};
|
|
14826
|
+
const outbox = args.outboxResult.ok ? {
|
|
14827
|
+
status: args.outboxResult.value.storage_initialized ? "ready" : "uninitialized",
|
|
14828
|
+
encryption_source: args.outboxResult.value.encryption_source,
|
|
14829
|
+
encryption_ready: args.outboxResult.value.encryption_ready,
|
|
14830
|
+
paused_at_ceiling: args.outboxResult.value.paused_at_ceiling,
|
|
14831
|
+
counts: args.outboxResult.value.counts,
|
|
14832
|
+
terminal_count: args.outboxResult.value.terminal_count,
|
|
14833
|
+
terminal_bytes: args.outboxResult.value.terminal_bytes,
|
|
14834
|
+
terminal_reasons: args.outboxResult.value.terminal_reasons,
|
|
14835
|
+
deletion_guidance: args.outboxResult.value.deletion_guidance
|
|
14836
|
+
} : {
|
|
14837
|
+
status: "unavailable",
|
|
14838
|
+
encryption_source: null,
|
|
14839
|
+
encryption_ready: false,
|
|
14840
|
+
paused_at_ceiling: null,
|
|
14841
|
+
counts: {},
|
|
14842
|
+
terminal_count: 0,
|
|
14843
|
+
terminal_bytes: 0,
|
|
14844
|
+
terminal_reasons: {},
|
|
14845
|
+
deletion_guidance: null
|
|
14846
|
+
};
|
|
14847
|
+
const ready = organization && args.report.registry_submission_authorized && policy.status === "ready" && outbox.encryption_ready === true && args.report.signed_contributions_ready === true && !outbox.paused_at_ceiling;
|
|
14848
|
+
const blocked = organization && (policy.status === "invalid" || policy.status === "authorization_required" || !args.report.registry_submission_authorized);
|
|
14849
|
+
return {
|
|
14850
|
+
status: !organization ? "not_applicable" : ready ? "ready" : blocked ? "blocked" : "attention",
|
|
14851
|
+
auto_capture_supported: true,
|
|
14852
|
+
organization_write_authorized: args.report.registry_submission_authorized,
|
|
14853
|
+
action: {
|
|
14854
|
+
name: "submit_private_lesson_candidate",
|
|
14855
|
+
visibility: "declared",
|
|
14856
|
+
host_approval: args.report.host_policy.status === "recent_denial" ? "recently_denied" : args.report.host_policy.status === "no_recent_denial" ? "no_recent_denial" : "not_observable"
|
|
14857
|
+
},
|
|
14858
|
+
policy,
|
|
14859
|
+
outbox,
|
|
14860
|
+
hold_telemetry_enabled: privateLessonHealthReportingEnabled(),
|
|
14861
|
+
signing_ready: args.report.signed_contributions_ready
|
|
14862
|
+
};
|
|
11994
14863
|
}
|
|
11995
14864
|
async function currentClientUpdate(access = resolveApiAccess()) {
|
|
11996
14865
|
const configuredSurface = process.env.REMEMBRANCE_PLUGIN_HOST?.trim().toLowerCase();
|
|
@@ -12014,7 +14883,7 @@ async function callDoctorReadProbe(definition, payload, access) {
|
|
|
12014
14883
|
}) : first;
|
|
12015
14884
|
}
|
|
12016
14885
|
function isTransientDoctorProbeFailure(result) {
|
|
12017
|
-
if (!
|
|
14886
|
+
if (!isRecord5(result) || result.ok === true) return false;
|
|
12018
14887
|
const status = typeof result.status === "number" ? result.status : 0;
|
|
12019
14888
|
return status === 0 || status === 408 || status === 425 || status >= 500;
|
|
12020
14889
|
}
|
|
@@ -12032,9 +14901,24 @@ function formatConnectionDoctorReport(report) {
|
|
|
12032
14901
|
`Registry submissions: ${report.registry_submission_authorized ? "authorized" : "not authorized"}`,
|
|
12033
14902
|
`Host policy observation: ${report.host_policy?.status ?? "not_observable"}`,
|
|
12034
14903
|
`Client update: ${clientUpdate.status}${clientUpdate.latest_version ? ` (published ${clientUpdate.latest_version})` : ""}`,
|
|
12035
|
-
`Signed contributions: ${report.signed_contributions_ready === null ? "not observable" : report.signed_contributions_ready ? "ready" : "not ready"}
|
|
12036
|
-
""
|
|
14904
|
+
`Signed contributions: ${report.signed_contributions_ready === null ? "not observable" : report.signed_contributions_ready ? "ready" : "not ready"}`
|
|
12037
14905
|
];
|
|
14906
|
+
if (report.private_lesson_automation) {
|
|
14907
|
+
lines.push(
|
|
14908
|
+
`Private lesson automation: ${report.private_lesson_automation.status}`,
|
|
14909
|
+
`Private lesson policy: ${report.private_lesson_automation.policy.status}`,
|
|
14910
|
+
`Private lesson outbox: ${report.private_lesson_automation.outbox.status}`,
|
|
14911
|
+
`Private lesson hold telemetry: ${report.private_lesson_automation.hold_telemetry_enabled ? "enabled" : "disabled"}`,
|
|
14912
|
+
`Private lesson terminal drafts: ${report.private_lesson_automation.outbox.terminal_count} (${report.private_lesson_automation.outbox.terminal_bytes} retained bytes)`,
|
|
14913
|
+
`Private lesson action: ${report.private_lesson_automation.action.visibility}`
|
|
14914
|
+
);
|
|
14915
|
+
if (report.private_lesson_automation.outbox.deletion_guidance) {
|
|
14916
|
+
lines.push(
|
|
14917
|
+
`Private lesson retained-draft action: ${report.private_lesson_automation.outbox.deletion_guidance}`
|
|
14918
|
+
);
|
|
14919
|
+
}
|
|
14920
|
+
}
|
|
14921
|
+
lines.push("");
|
|
12038
14922
|
for (const check of report.checks) {
|
|
12039
14923
|
lines.push(`[${check.status.toUpperCase()}] ${check.id}: ${check.summary}`);
|
|
12040
14924
|
if (check.remediation) {
|
|
@@ -12275,6 +15159,11 @@ async function ensurePrincipalSessionToken(access = resolveApiAccess()) {
|
|
|
12275
15159
|
};
|
|
12276
15160
|
return cachedPrincipalSession.token;
|
|
12277
15161
|
}
|
|
15162
|
+
function warmPrincipalSession(access) {
|
|
15163
|
+
const task = ensurePrincipalSessionToken(access).then(() => void 0).catch(() => void 0);
|
|
15164
|
+
principalSessionBackgroundTasks.add(task);
|
|
15165
|
+
void task.finally(() => principalSessionBackgroundTasks.delete(task));
|
|
15166
|
+
}
|
|
12278
15167
|
function currentPrincipalSessionToken(access) {
|
|
12279
15168
|
if (!cachedPrincipalSession) return null;
|
|
12280
15169
|
if (cachedPrincipalSession.accessKey !== apiAccessKey(access) || cachedPrincipalSession.expiresAt <= Date.now() + 6e4) {
|
|
@@ -12303,7 +15192,7 @@ async function directPrincipalSessionRequest(payload, access) {
|
|
|
12303
15192
|
}
|
|
12304
15193
|
}
|
|
12305
15194
|
function normalizedMcpClientInfo(value) {
|
|
12306
|
-
if (!
|
|
15195
|
+
if (!isRecord5(value)) return null;
|
|
12307
15196
|
const name = boundedLocalProfileField(value.name);
|
|
12308
15197
|
if (!name) return null;
|
|
12309
15198
|
return {
|
|
@@ -12356,7 +15245,7 @@ function mustFindTool(name) {
|
|
|
12356
15245
|
}
|
|
12357
15246
|
return tool2;
|
|
12358
15247
|
}
|
|
12359
|
-
function
|
|
15248
|
+
function isRecord5(value) {
|
|
12360
15249
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
12361
15250
|
}
|
|
12362
15251
|
function identityPath(explicit) {
|
|
@@ -12413,5 +15302,6 @@ export {
|
|
|
12413
15302
|
resetPrincipalSessionCacheForTests,
|
|
12414
15303
|
resetValueProofKeyCacheForTests,
|
|
12415
15304
|
runConnectionDoctorCli,
|
|
12416
|
-
runLocalConnectionDoctor
|
|
15305
|
+
runLocalConnectionDoctor,
|
|
15306
|
+
waitForPrincipalSessionBackgroundTasksForTests
|
|
12417
15307
|
};
|