@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.
@@ -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
- if (!manifest.surfaces.includes(input.surface)) {
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(manifest.latest_version, input.currentVersion) <= 0) {
4592
+ if (compareClientVersions(release.version, input.currentVersion) <= 0) {
4573
4593
  return {
4574
4594
  status: "current",
4575
4595
  current_version: input.currentVersion,
4576
- latest_version: manifest.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: manifest.latest_version,
4603
+ latest_version: release.version,
4584
4604
  guidance,
4585
4605
  notice: clientUpdateNotice({
4586
4606
  currentVersion: input.currentVersion,
4587
- latestVersion: manifest.latest_version,
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
- rationale: external_exports.string().trim().min(1).max(200),
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.19",
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.19",
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 = verify(
9223
+ const verified = verify2(
8344
9224
  null,
8345
9225
  Buffer.from(canonicalJson(proof.payload)),
8346
- createPublicKey({
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 existsSync3 } from "node:fs";
8498
- import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
8499
- import { dirname as dirname2 } from "node:path";
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. Explicit user preferences apply immediately; inferred observations activate only after Remembrance's consistency and confidence thresholds are met.",
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: false,
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
- return definitions?.[name] ?? converted;
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 createPublicKey2 } from "node:crypto";
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 = createPublicKey2(value.public_key).export({
11209
+ const publicDer = createPublicKey3(value.public_key).export({
10253
11210
  type: "spki",
10254
11211
  format: "der"
10255
11212
  });
10256
- const derivedPublicDer = createPublicKey2(
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() ?? shared.config.memberLinkToken ?? ""
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
- let marker = null;
10706
- let newestMarkerMs = Number.NEGATIVE_INFINITY;
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 candidateMs = typeof parsed.last_seen_at === "string" ? Date.parse(parsed.last_seen_at) : Number.NEGATIVE_INFINITY;
10716
- if (!marker || candidateMs > newestMarkerMs) {
10717
- marker = parsed;
10718
- newestMarkerMs = candidateMs;
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 (!marker) {
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 componentRecord = isRecord3(marker.components) ? marker.components : {};
10735
- const nowMs = (options.now ?? /* @__PURE__ */ new Date()).getTime();
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. SessionStart and the prompt hook must run first; tool and completion observations appear after those lifecycle events become eligible."
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
- if (fileSystem.exists(legacyPath)) sessionPaths.push(legacyPath);
10932
- return [...new Set(sessionPaths)].slice(0, MAX_LOCAL_LIFECYCLE_MARKERS);
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/server.ts
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.64" : "0.0.0-dev";
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
- void ensurePrincipalSessionToken(access).catch(() => null);
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 = isRecord4(status.plugin_health) ? status.plugin_health : null;
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 = isRecord4(health.components) ? health.components : {};
11400
- const observed = (name) => isRecord4(components[name]) && components[name]?.observed === true ? "active" : "not_observed";
11401
- const issueCodes2 = Array.isArray(health.issues) ? health.issues.map((issue) => isRecord4(issue) ? issue.code : null).filter((code) => typeof code === "string") : [];
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 (/^(0|false|no)$/i.test(process.env.REMEMBRANCE_HEALTH_REPORTING ?? "")) {
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 && existsSync3(keyPath);
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 mkdir2(dirname2(keyPath), { recursive: true, mode: 448 });
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 (!existsSync3(path)) {
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
- void ensurePrincipalSessionToken(access).catch(() => null);
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
- void ensurePrincipalSessionToken(access).catch(() => null);
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 (!isRecord4(body) || typeof body.error !== "string") return false;
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 [connectionResult, catalogResult, clientUpdate] = await Promise.all([
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 = isRecord4(catalogResult) ? catalogResult : {};
11979
- const body = isRecord4(resultRecord.body) ? resultRecord.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
- return buildConnectionDoctorReport({
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 (!isRecord4(result) || result.ok === true) return false;
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 (!isRecord4(value)) return null;
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 isRecord4(value) {
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
  };