agentplugged 1.0.6 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -4
- package/dist/agent-commands.js +6796 -510
- package/dist/forge-follow.js +3 -3
- package/dist/{webauthn.js → hash.js} +3624 -549
- package/dist/helpers.js +723 -294
- package/dist/launcher.js +2 -2
- package/dist/main.js +358 -47
- package/dist/method/agents/noah/SKILL.md +19 -17
- package/dist/method/agents/noah/references/conduct.md +4 -1
- package/dist/method/agents/noah/references/decisions.md +1 -0
- package/dist/method/models.yaml +0 -11
- package/dist/method/skills/workshop/references/create.md +1 -1
- package/dist/product-root.js +15601 -15076
- package/dist/rolldown-runtime.js +1 -1
- package/dist/webauthn-verify.js +112 -1
- package/dist/webauthn-verify2.js +1 -107
- package/package.json +11 -11
|
@@ -483,6 +483,11 @@ const ErrorCode = {
|
|
|
483
483
|
*/
|
|
484
484
|
DELEGATION_INVALID: "DELEGATION_INVALID",
|
|
485
485
|
/**
|
|
486
|
+
* `pause_team`: the team is already paused, there is nothing more to hold. Nothing was
|
|
487
|
+
* written. `details: { since }`.
|
|
488
|
+
*/
|
|
489
|
+
TEAM_ALREADY_PAUSED: "TEAM_ALREADY_PAUSED",
|
|
490
|
+
/**
|
|
486
491
|
* A review cannot start, be given, sorted, read or posted (`start_review`,
|
|
487
492
|
* `submit_review`, `triage_review`, `get_review`, `publish_review`). Nothing was written.
|
|
488
493
|
* `details: { story, reason, … }` (`REVIEW_NOT_POSSIBLE_REASONS`).
|
|
@@ -695,6 +700,16 @@ const EVENT_CONVERSIONS = [
|
|
|
695
700
|
from: 13,
|
|
696
701
|
convert: (payload) => ({ ...payload })
|
|
697
702
|
},
|
|
703
|
+
{
|
|
704
|
+
type: "checkpoint.opened",
|
|
705
|
+
from: 14,
|
|
706
|
+
convert: (payload) => ({ ...payload })
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
type: "checkpoint.opened",
|
|
710
|
+
from: 15,
|
|
711
|
+
convert: (payload) => ({ ...payload })
|
|
712
|
+
},
|
|
698
713
|
{
|
|
699
714
|
type: "creator_action.executed",
|
|
700
715
|
from: 11,
|
|
@@ -715,6 +730,26 @@ const EVENT_CONVERSIONS = [
|
|
|
715
730
|
from: 2,
|
|
716
731
|
convert: (payload) => ({ ...payload })
|
|
717
732
|
},
|
|
733
|
+
{
|
|
734
|
+
type: "story.merged",
|
|
735
|
+
from: 3,
|
|
736
|
+
convert: (payload) => ({ ...payload })
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
type: "story.merged",
|
|
740
|
+
from: 4,
|
|
741
|
+
convert: (payload) => ({ ...payload })
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
type: "checkpoint.web_answered",
|
|
745
|
+
from: 1,
|
|
746
|
+
convert: (payload) => ({ ...payload })
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
type: "checkpoint.web_held",
|
|
750
|
+
from: 1,
|
|
751
|
+
convert: (payload) => ({ ...payload })
|
|
752
|
+
},
|
|
718
753
|
{
|
|
719
754
|
type: "checkpoint.closed",
|
|
720
755
|
from: 1,
|
|
@@ -756,19 +791,21 @@ const EVENT_CONVERSIONS = [
|
|
|
756
791
|
* raised when a payload gains fields a strict schema of an older engine would refuse: that
|
|
757
792
|
* engine then asks for an upgrade (`UPGRADE_REQUIRED`) instead of ignoring the event as invalid.
|
|
758
793
|
* `checkpoint.opened` is at 2 and at 3 and at 4
|
|
759
|
-
* and at 5 and at 6 and at 7 and at 8 and at 9 and at 10 and at 11 and at 12 and at 13 and at 14, `requirement.proved` at 2, `story.merged` at 2 and at
|
|
794
|
+
* and at 5 and at 6 and at 7 and at 8 and at 9 and at 10 and at 11 and at 12 and at 13 and at 14 and at 15 and at 16, `requirement.proved` at 2, `story.merged` at 2, at 3, at 4 and at 5, `checkpoint.web_answered` and `checkpoint.web_held` at 2, `cost.reported` at 2,
|
|
760
795
|
* `creator_action.executed` at 2, at 3, at 4 and at 5 and at 6 and at 7 and at 8 and at 9 and at 10 and at 11 and at 12, `checkpoint.closed` at 2 (and `checkpoint.answered` at 2),
|
|
761
796
|
* `checkpoint.elicited` and `stack.answered` at 2, `usage.measured` and
|
|
762
797
|
* `agent.status_reported` at 2.
|
|
763
798
|
*/
|
|
764
799
|
const EVENT_TYPE_VERSIONS = {
|
|
765
|
-
"checkpoint.opened":
|
|
800
|
+
"checkpoint.opened": 16,
|
|
766
801
|
"checkpoint.closed": 2,
|
|
767
802
|
"checkpoint.answered": 2,
|
|
768
803
|
"cost.reported": 2,
|
|
769
804
|
"requirement.proved": 2,
|
|
770
805
|
"creator_action.executed": 12,
|
|
771
|
-
"story.merged":
|
|
806
|
+
"story.merged": 5,
|
|
807
|
+
"checkpoint.web_answered": 2,
|
|
808
|
+
"checkpoint.web_held": 2,
|
|
772
809
|
"checkpoint.elicited": 2,
|
|
773
810
|
"stack.answered": 2,
|
|
774
811
|
"usage.measured": 2,
|
|
@@ -785,6 +822,20 @@ function eventConversion(type, version) {
|
|
|
785
822
|
return EVENT_CONVERSIONS.find((entry) => entry.type === type && entry.from === version);
|
|
786
823
|
}
|
|
787
824
|
//#endregion
|
|
825
|
+
//#region ../../packages/protocol/dist/extension.js
|
|
826
|
+
/** The name of a module: lowercase letters, digits and underscores, starting with a letter. */
|
|
827
|
+
const EXTENSION_MODULE_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
828
|
+
/** `x.<module>.<type>`, lowercase. */
|
|
829
|
+
const EXTENSION_TYPE_PATTERN = /^x\.[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*$/;
|
|
830
|
+
/** True for a type of the extension point (`x.<module>.<type>`). */
|
|
831
|
+
function isExtensionEventType(type) {
|
|
832
|
+
return EXTENSION_TYPE_PATTERN.test(type);
|
|
833
|
+
}
|
|
834
|
+
/** The module an extension type belongs to, or undefined for any other type. */
|
|
835
|
+
function extensionModuleOf(type) {
|
|
836
|
+
return isExtensionEventType(type) ? type.split(".")[1] : void 0;
|
|
837
|
+
}
|
|
838
|
+
//#endregion
|
|
788
839
|
//#region ../../packages/protocol/dist/event-envelope.js
|
|
789
840
|
/** True when `type` can be converted from `version` up to its current version. */
|
|
790
841
|
function convertible(type, version) {
|
|
@@ -860,8 +911,8 @@ const BASE64URL = /^[A-Za-z0-9_-]*$/;
|
|
|
860
911
|
/** ULID: 26 Crockford base32 characters, the first one at most 7 (48-bit time). */
|
|
861
912
|
const ULID_PATTERN = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/;
|
|
862
913
|
const ULID = ULID_PATTERN;
|
|
863
|
-
/** `domain.action`, lowercase. */
|
|
864
|
-
const EVENT_TYPE_PATTERN =
|
|
914
|
+
/** `domain.action`, lowercase; or `x.<module>.<type>` for the extension point. */
|
|
915
|
+
const EVENT_TYPE_PATTERN = new RegExp(`^(?:[a-z][a-z0-9_]*\\.[a-z][a-z0-9_]*|${EXTENSION_TYPE_PATTERN.source.slice(1, -1)})$`);
|
|
865
916
|
/** `sha256:` then the 32-byte digest in base64url (43 characters). */
|
|
866
917
|
const EVENT_HASH = /^sha256:[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/;
|
|
867
918
|
/** 64 bytes in base64url: 86 characters, the last one carrying 2 bits only. */
|
|
@@ -915,7 +966,7 @@ const WebAuthnSignatureSchema = z.strictObject({
|
|
|
915
966
|
});
|
|
916
967
|
/** Event envelope v1: one event of the journal or of a validation, signed by its writer. */
|
|
917
968
|
const EventEnvelopeSchema = z.strictObject({
|
|
918
|
-
v: z.number().int().min(0)
|
|
969
|
+
v: z.number().int().min(0),
|
|
919
970
|
id: UlidSchema,
|
|
920
971
|
type: z.string().regex(EVENT_TYPE_PATTERN, { message: EVENT_REASON.eventType }),
|
|
921
972
|
at: z.iso.datetime({ message: EVENT_REASON.utcDatetime }),
|
|
@@ -930,6 +981,11 @@ const EventEnvelopeSchema = z.strictObject({
|
|
|
930
981
|
sig: z.union([z.string(), z.record(z.string(), z.unknown())]),
|
|
931
982
|
assurance: z.enum(ASSURANCE_LEVELS).optional()
|
|
932
983
|
}).superRefine((event, context) => {
|
|
984
|
+
if (event.v > MAX_EVENT_VERSION && !isExtensionEventType(event.type)) context.addIssue({
|
|
985
|
+
code: "custom",
|
|
986
|
+
path: ["v"],
|
|
987
|
+
message: EVENT_REASON.invalidValue
|
|
988
|
+
});
|
|
933
989
|
if (event.v < currentVersionOf(event.type) && !convertible(event.type, event.v)) context.addIssue({
|
|
934
990
|
code: "custom",
|
|
935
991
|
path: ["v"],
|
|
@@ -1040,7 +1096,7 @@ function eventInvalid(field, reasonKey) {
|
|
|
1040
1096
|
function parseEventEnvelope(input) {
|
|
1041
1097
|
if (typeof input === "object" && input !== null && !Array.isArray(input)) {
|
|
1042
1098
|
const { v: version, type } = input;
|
|
1043
|
-
if (typeof version === "number" && Number.isInteger(version) && version > MAX_EVENT_VERSION) return {
|
|
1099
|
+
if (typeof version === "number" && Number.isInteger(version) && version > MAX_EVENT_VERSION && !(typeof type === "string" && isExtensionEventType(type))) return {
|
|
1044
1100
|
success: false,
|
|
1045
1101
|
error: {
|
|
1046
1102
|
code: ErrorCode.UPGRADE_REQUIRED,
|
|
@@ -1307,16 +1363,19 @@ const MODEL_TIERS = [
|
|
|
1307
1363
|
"multimodal"
|
|
1308
1364
|
];
|
|
1309
1365
|
/**
|
|
1310
|
-
* Clients of the model table: the AI tools as they read a sub-agent.
|
|
1311
|
-
* `gemini-cli` are both served by the `antigravity` entry of `tools`.
|
|
1366
|
+
* Clients of the model table: the AI tools as they read a sub-agent.
|
|
1312
1367
|
*/
|
|
1313
1368
|
const MODEL_CLIENT_IDS = [
|
|
1314
1369
|
"claude-code",
|
|
1315
1370
|
"codex",
|
|
1316
1371
|
"antigravity",
|
|
1317
|
-
"gemini-cli",
|
|
1318
1372
|
"cursor"
|
|
1319
1373
|
];
|
|
1374
|
+
/**
|
|
1375
|
+
* Key of the retired Gemini CLI in a model table or in the `models` section of `method.yaml`.
|
|
1376
|
+
* Older products still hold it: it is read, reported and ignored, never accepted for anything new.
|
|
1377
|
+
*/
|
|
1378
|
+
const LEGACY_GEMINI_CLIENT = "gemini-cli";
|
|
1320
1379
|
/** Model value that keeps the model of the session: no model is chosen per agent. */
|
|
1321
1380
|
const INHERIT_MODEL = "inherit";
|
|
1322
1381
|
const Value = z.string().trim().min(1);
|
|
@@ -1369,7 +1428,7 @@ const ModelTableSchema = z.strictObject({
|
|
|
1369
1428
|
"claude-code": tiers(ClaudeCell),
|
|
1370
1429
|
codex: tiers(CodexCell),
|
|
1371
1430
|
antigravity: tiers(ModelOnly),
|
|
1372
|
-
|
|
1431
|
+
[LEGACY_GEMINI_CLIENT]: z.unknown().optional(),
|
|
1373
1432
|
cursor: tiers(ModelOnly)
|
|
1374
1433
|
});
|
|
1375
1434
|
function partialTiers(cell) {
|
|
@@ -1394,7 +1453,7 @@ const ModelOverridesSchema = z.strictObject({
|
|
|
1394
1453
|
effort: z.enum([...CODEX_EFFORTS, NO_EFFORT]).optional()
|
|
1395
1454
|
})),
|
|
1396
1455
|
antigravity: partialTiers(ModelOnly.partial()),
|
|
1397
|
-
|
|
1456
|
+
[LEGACY_GEMINI_CLIENT]: z.unknown().optional(),
|
|
1398
1457
|
cursor: partialTiers(ModelOnly.partial())
|
|
1399
1458
|
});
|
|
1400
1459
|
const SemverSchema = z.string().regex(/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/);
|
|
@@ -1483,6 +1542,48 @@ const ProfileSchema = z.strictObject({
|
|
|
1483
1542
|
message: "must be empty while the code domain is inactive"
|
|
1484
1543
|
});
|
|
1485
1544
|
});
|
|
1545
|
+
/** The starting profiles. */
|
|
1546
|
+
const PROFILE_PRESET_SPECS = {
|
|
1547
|
+
"new-product": {
|
|
1548
|
+
domains: DOMAIN_IDS,
|
|
1549
|
+
phases: PHASE_IDS,
|
|
1550
|
+
specialties: ["backend", "frontend"]
|
|
1551
|
+
},
|
|
1552
|
+
maintenance: {
|
|
1553
|
+
domains: [
|
|
1554
|
+
"code",
|
|
1555
|
+
"quality",
|
|
1556
|
+
"security",
|
|
1557
|
+
"delivery",
|
|
1558
|
+
"docs"
|
|
1559
|
+
],
|
|
1560
|
+
phases: [
|
|
1561
|
+
"design",
|
|
1562
|
+
"build",
|
|
1563
|
+
"delivery"
|
|
1564
|
+
],
|
|
1565
|
+
specialties: ["backend", "frontend"]
|
|
1566
|
+
},
|
|
1567
|
+
"launch-only": {
|
|
1568
|
+
domains: [
|
|
1569
|
+
"product",
|
|
1570
|
+
"creativity",
|
|
1571
|
+
"marketing",
|
|
1572
|
+
"sales"
|
|
1573
|
+
],
|
|
1574
|
+
phases: ["framing", "launch"],
|
|
1575
|
+
specialties: []
|
|
1576
|
+
},
|
|
1577
|
+
prototype: {
|
|
1578
|
+
domains: [
|
|
1579
|
+
"product",
|
|
1580
|
+
"ux",
|
|
1581
|
+
"code"
|
|
1582
|
+
],
|
|
1583
|
+
phases: ["framing", "build"],
|
|
1584
|
+
specialties: ["backend", "frontend"]
|
|
1585
|
+
}
|
|
1586
|
+
};
|
|
1486
1587
|
function idList() {
|
|
1487
1588
|
return z.array(SkillNameSchema).refine((list) => new Set(list).size === list.length, { message: ISSUE.duplicateValue });
|
|
1488
1589
|
}
|
|
@@ -1561,6 +1662,10 @@ const RulesSchema = z.strictObject({
|
|
|
1561
1662
|
});
|
|
1562
1663
|
/** What going over a story's ceiling does: pause it behind a checkpoint, or flag it. */
|
|
1563
1664
|
const BUDGET_EXCEED_MODES = ["block", "flag"];
|
|
1665
|
+
/** The alert ratio without a setting: 80 % of the ceiling. */
|
|
1666
|
+
const DEFAULT_ALERT_RATIO = .8;
|
|
1667
|
+
/** What going over the ceiling does without a setting. */
|
|
1668
|
+
const DEFAULT_ON_EXCEED = "block";
|
|
1564
1669
|
/** The lowest alert ratio a product may set. */
|
|
1565
1670
|
const MIN_ALERT_RATIO = .05;
|
|
1566
1671
|
/** `budgets` of `method.yaml`: the alert ratio, and what going over the ceiling does. */
|
|
@@ -3590,7 +3695,7 @@ function checkpointIdOf(eventId) {
|
|
|
3590
3695
|
/** A free text of 1 to `max` characters. */
|
|
3591
3696
|
const text = (max) => z.string().trim().min(1).max(max);
|
|
3592
3697
|
/** A short identifier: an option or a criterion. */
|
|
3593
|
-
const ShortIdSchema$
|
|
3698
|
+
const ShortIdSchema$4 = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$/, { message: "expected 1 to 32 letters, digits, dots, hyphens or underscores" });
|
|
3594
3699
|
/** A measure's name: `tests_failed`, `coverage`, `lighthouse.performance`. */
|
|
3595
3700
|
const MetricSchema = z.string().regex(/^[a-z][a-z0-9_.]{0,63}$/, { message: "expected a lowercase name of letters, digits, dots and underscores" });
|
|
3596
3701
|
/**
|
|
@@ -3619,13 +3724,13 @@ const CheckpointBlockSchema = z.strictObject({
|
|
|
3619
3724
|
});
|
|
3620
3725
|
/** One answer of a decision: what it is, and what it entails. */
|
|
3621
3726
|
const CheckpointOptionSchema = z.strictObject({
|
|
3622
|
-
id: ShortIdSchema$
|
|
3727
|
+
id: ShortIdSchema$4,
|
|
3623
3728
|
label: text(200),
|
|
3624
3729
|
consequence: text(1e3)
|
|
3625
3730
|
});
|
|
3626
3731
|
/** A rule on a measure: `<metric> <op> <threshold>`. */
|
|
3627
3732
|
const CheckpointCriterionSchema = z.strictObject({
|
|
3628
|
-
id: ShortIdSchema$
|
|
3733
|
+
id: ShortIdSchema$4,
|
|
3629
3734
|
metric: MetricSchema,
|
|
3630
3735
|
op: z.enum(CRITERION_OPERATORS),
|
|
3631
3736
|
threshold: z.number().finite(),
|
|
@@ -3633,16 +3738,38 @@ const CheckpointCriterionSchema = z.strictObject({
|
|
|
3633
3738
|
});
|
|
3634
3739
|
/** Measures reported for a checkpoint: `{ <metric>: number }`, 1 to 50 of them. */
|
|
3635
3740
|
const MeasuresSchema = z.record(MetricSchema, z.number().finite()).refine((measures) => Object.keys(measures).length >= 1, { message: "give at least a measure" }).refine((measures) => Object.keys(measures).length <= 50, { message: "give at most 50 measures" });
|
|
3741
|
+
/**
|
|
3742
|
+
* What the creator reads first on a checkpoint, in plain words: what changes for the people who
|
|
3743
|
+
* use the product (`effect`), what could go wrong (`risks`) and what the agents consulted think
|
|
3744
|
+
* (`opinions`, one sentence each). Optional: a checkpoint without a card is read from its
|
|
3745
|
+
* `summary`. The fuller text stays in `summary`, one click away. `why` answers "why this?" in
|
|
3746
|
+
* place: one sentence for the reason, one for the alternative set aside.
|
|
3747
|
+
*/
|
|
3748
|
+
const CheckpointCardSchema = z.strictObject({
|
|
3749
|
+
effect: text(300),
|
|
3750
|
+
why: z.strictObject({
|
|
3751
|
+
reason: text(200),
|
|
3752
|
+
/** Absent when nothing else was weighed. */
|
|
3753
|
+
rejected: text(200).optional()
|
|
3754
|
+
}).optional(),
|
|
3755
|
+
risks: z.array(text(200)).min(1).max(3).optional(),
|
|
3756
|
+
opinions: z.array(z.strictObject({
|
|
3757
|
+
agent: AgentIdSchema.max(64),
|
|
3758
|
+
view: text(200)
|
|
3759
|
+
})).min(1).max(4).optional()
|
|
3760
|
+
});
|
|
3636
3761
|
/** The fields an agent gives, whose defaults `core` fixes (recipient, confirmation). */
|
|
3637
3762
|
const describedFields = {
|
|
3638
3763
|
type: z.enum(CHECKPOINT_TYPES),
|
|
3639
3764
|
risk: z.enum(CHECKPOINT_RISKS),
|
|
3640
3765
|
title: text(120),
|
|
3641
3766
|
summary: text(2e3),
|
|
3767
|
+
/** The first lines the creator reads: the effect for the users, the risks, the agents' views. */
|
|
3768
|
+
card: CheckpointCardSchema.optional(),
|
|
3642
3769
|
documents: z.array(CheckpointDocumentSchema).max(20),
|
|
3643
3770
|
blocks: z.array(CheckpointBlockSchema).max(10),
|
|
3644
3771
|
options: z.array(CheckpointOptionSchema).min(2).max(4).optional(),
|
|
3645
|
-
recommendation: ShortIdSchema$
|
|
3772
|
+
recommendation: ShortIdSchema$4.optional(),
|
|
3646
3773
|
criteria: z.array(CheckpointCriterionSchema).min(1).max(20).optional(),
|
|
3647
3774
|
/** The phase a `phase_end` checkpoint closes: given with that type, and only with it. */
|
|
3648
3775
|
phase: z.enum(PHASE_IDS).optional()
|
|
@@ -3903,7 +4030,7 @@ const NOTE_REQUIRED = /* @__PURE__ */ new Set([
|
|
|
3903
4030
|
const CheckpointResolvedPayloadSchema = z.strictObject({
|
|
3904
4031
|
checkpoint: CheckpointIdSchema,
|
|
3905
4032
|
decision: z.enum(RESOLUTION_DECISIONS),
|
|
3906
|
-
option: ShortIdSchema$
|
|
4033
|
+
option: ShortIdSchema$4.optional(),
|
|
3907
4034
|
note: text(2e3).optional(),
|
|
3908
4035
|
waiver_until: z.iso.datetime().optional(),
|
|
3909
4036
|
source: z.enum(RESOLUTION_SOURCES)
|
|
@@ -4807,6 +4934,117 @@ const QaReportedPayloadSchema = z.strictObject({
|
|
|
4807
4934
|
path: ["results"]
|
|
4808
4935
|
});
|
|
4809
4936
|
//#endregion
|
|
4937
|
+
//#region ../../packages/protocol/dist/account.js
|
|
4938
|
+
/**
|
|
4939
|
+
* The wire contract of the account routes of the relay: creating an account (the address is proved
|
|
4940
|
+
* by a code sent by e-mail, then a passkey is made), signing in with a passkey, reading and
|
|
4941
|
+
* changing the profile, signing out. No password exists anywhere in it: a passkey is the only way
|
|
4942
|
+
* in. The address is unique: one person, one account.
|
|
4943
|
+
*/
|
|
4944
|
+
/** The languages an account can choose. */
|
|
4945
|
+
const ACCOUNT_LOCALES = [
|
|
4946
|
+
"fr",
|
|
4947
|
+
"en",
|
|
4948
|
+
"es",
|
|
4949
|
+
"pt"
|
|
4950
|
+
];
|
|
4951
|
+
/** An address as the relay keeps it: trimmed and lowercased. */
|
|
4952
|
+
const AccountEmailSchema = z.string().transform((value) => value.trim().toLowerCase()).pipe(z.string().max(254).regex(/^[^\s@]+@[^\s@]+\.[^\s@]+$/));
|
|
4953
|
+
const AccountLocaleSchema = z.enum(ACCOUNT_LOCALES);
|
|
4954
|
+
z.strictObject({
|
|
4955
|
+
email: AccountEmailSchema,
|
|
4956
|
+
locale: AccountLocaleSchema
|
|
4957
|
+
});
|
|
4958
|
+
z.strictObject({
|
|
4959
|
+
verification: z.string().min(1).max(64),
|
|
4960
|
+
/** How long the code works, in minutes. */
|
|
4961
|
+
valid_minutes: z.number().int().positive()
|
|
4962
|
+
});
|
|
4963
|
+
z.strictObject({
|
|
4964
|
+
verification: z.string().min(1).max(64),
|
|
4965
|
+
code: z.string().regex(new RegExp(`^[0-9]{6}$`))
|
|
4966
|
+
});
|
|
4967
|
+
z.strictObject({ verification: z.string().min(1).max(64) });
|
|
4968
|
+
z.strictObject({});
|
|
4969
|
+
z.object({
|
|
4970
|
+
ceremony: z.string().min(1).max(64),
|
|
4971
|
+
options: z.record(z.string(), z.unknown())
|
|
4972
|
+
});
|
|
4973
|
+
z.strictObject({
|
|
4974
|
+
ceremony: z.string().min(1).max(64),
|
|
4975
|
+
response: z.record(z.string(), z.unknown())
|
|
4976
|
+
});
|
|
4977
|
+
z.strictObject({ ok: z.literal(true) });
|
|
4978
|
+
z.strictObject({
|
|
4979
|
+
ok: z.literal(true),
|
|
4980
|
+
session: z.string().min(1).max(128),
|
|
4981
|
+
expires_at: z.string()
|
|
4982
|
+
});
|
|
4983
|
+
z.strictObject({
|
|
4984
|
+
account_id: z.string().regex(ULID_PATTERN),
|
|
4985
|
+
email: z.string().nullable(),
|
|
4986
|
+
locale: AccountLocaleSchema.nullable(),
|
|
4987
|
+
created_at: z.string()
|
|
4988
|
+
});
|
|
4989
|
+
z.strictObject({ locale: AccountLocaleSchema });
|
|
4990
|
+
/** An export of the account's data, as the relay describes it to its owner. */
|
|
4991
|
+
const AccountExportStatusSchema = z.strictObject({
|
|
4992
|
+
id: z.string().regex(ULID_PATTERN),
|
|
4993
|
+
status: z.enum(["pending", "ready"]),
|
|
4994
|
+
requested_at: z.string(),
|
|
4995
|
+
/** The date the export is promised by (it is usually ready within seconds). */
|
|
4996
|
+
due_at: z.string(),
|
|
4997
|
+
ready_at: z.string().nullable(),
|
|
4998
|
+
/** The date the link stops working, once the export is ready. */
|
|
4999
|
+
expires_at: z.string().nullable()
|
|
5000
|
+
});
|
|
5001
|
+
z.strictObject({ export: AccountExportStatusSchema.nullable() });
|
|
5002
|
+
z.strictObject({ id: z.string().regex(ULID_PATTERN) });
|
|
5003
|
+
z.strictObject({ confirm: z.literal(true) });
|
|
5004
|
+
/** One passkey of the account, as its owner sees it: public facts only. */
|
|
5005
|
+
const AccountPasskeySchema = z.strictObject({
|
|
5006
|
+
/** The credential identifier, base64url (public: it is not a secret). */
|
|
5007
|
+
id: z.string().min(1).max(1024),
|
|
5008
|
+
created_at: z.string(),
|
|
5009
|
+
last_used_at: z.string().nullable()
|
|
5010
|
+
});
|
|
5011
|
+
z.strictObject({ passkeys: z.array(AccountPasskeySchema).max(10) });
|
|
5012
|
+
z.strictObject({ id: z.string().min(1).max(1024) });
|
|
5013
|
+
//#endregion
|
|
5014
|
+
//#region ../../packages/protocol/dist/account-export.js
|
|
5015
|
+
/**
|
|
5016
|
+
* The file a creator receives when they export their account: everything the relay holds about it,
|
|
5017
|
+
* in indented JSON, checked by this schema (`account-export.v1`). It never holds a secret and never
|
|
5018
|
+
* a readable content: the encrypted packets and vaults appear with their dates and sizes only, and
|
|
5019
|
+
* the relay cannot read them. What a product holds (documents, stories, history) lives in the
|
|
5020
|
+
* creator's repository and is not part of it.
|
|
5021
|
+
*/
|
|
5022
|
+
const ACCOUNT_EXPORT_SCHEMA_ID = "account-export.v1";
|
|
5023
|
+
const Scalar = z.union([
|
|
5024
|
+
z.string(),
|
|
5025
|
+
z.number(),
|
|
5026
|
+
z.boolean(),
|
|
5027
|
+
z.null()
|
|
5028
|
+
]);
|
|
5029
|
+
const Row = z.record(z.string(), Scalar);
|
|
5030
|
+
z.strictObject({
|
|
5031
|
+
schema: z.literal(ACCOUNT_EXPORT_SCHEMA_ID),
|
|
5032
|
+
generated_at: z.string(),
|
|
5033
|
+
/** What is not in this file, and where it is. */
|
|
5034
|
+
not_included: z.string(),
|
|
5035
|
+
account: Row,
|
|
5036
|
+
passkeys: z.array(Row),
|
|
5037
|
+
sessions: z.array(Row),
|
|
5038
|
+
devices: z.array(Row),
|
|
5039
|
+
device_prekeys: z.array(Row),
|
|
5040
|
+
pending_envelopes: z.array(Row),
|
|
5041
|
+
seen_envelopes: z.array(Row),
|
|
5042
|
+
vaults: z.array(Row),
|
|
5043
|
+
shares: z.array(Row),
|
|
5044
|
+
push_tokens: z.array(Row),
|
|
5045
|
+
exports: z.array(Row)
|
|
5046
|
+
});
|
|
5047
|
+
//#endregion
|
|
4810
5048
|
//#region ../../packages/protocol/dist/skill-definition.js
|
|
4811
5049
|
/** Risk level of an agent's actions. */
|
|
4812
5050
|
const RISK_LEVELS = [
|
|
@@ -4952,49 +5190,93 @@ const AgentOverrideSchema = z.strictObject({
|
|
|
4952
5190
|
voice: customisable.voice.optional()
|
|
4953
5191
|
});
|
|
4954
5192
|
//#endregion
|
|
4955
|
-
//#region ../../packages/protocol/dist/
|
|
4956
|
-
/**
|
|
4957
|
-
*
|
|
4958
|
-
*
|
|
4959
|
-
*
|
|
4960
|
-
*
|
|
4961
|
-
*
|
|
4962
|
-
*
|
|
4963
|
-
*
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
/** The
|
|
4968
|
-
const
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
5193
|
+
//#region ../../packages/protocol/dist/autonomy.js
|
|
5194
|
+
/**
|
|
5195
|
+
* The autonomy dial of a product: three notches that set, in one gesture, how much the creator
|
|
5196
|
+
* keeps for themselves. The dial is a preset over settings the engine already has (the validation
|
|
5197
|
+
* level, the points the creator keeps, the installs mode, two permission rules); it adds no rule
|
|
5198
|
+
* of its own. Raising it never lifts a locked rule, the irreversible commands or the base guards
|
|
5199
|
+
* (secrets, pushes to the main branch), and never makes an agent validate for the creator:
|
|
5200
|
+
* the merge, the release and the publications are not touched by any notch.
|
|
5201
|
+
*
|
|
5202
|
+
* Everything here is data and pure functions: the machine writes the settings with the engine's own
|
|
5203
|
+
* writers, and the web view reads the same table to say what a notch changes.
|
|
5204
|
+
*/
|
|
5205
|
+
/** The notches, from the most careful to the most free. */
|
|
5206
|
+
const AUTONOMY_DIALS = [
|
|
5207
|
+
"prudent",
|
|
5208
|
+
"balanced",
|
|
5209
|
+
"wide"
|
|
5210
|
+
];
|
|
5211
|
+
const AutonomyDialSchema = z.enum(AUTONOMY_DIALS);
|
|
5212
|
+
/** What a product's settings add up to: a notch, or `custom` (set by hand or from a terminal). */
|
|
5213
|
+
const AUTONOMY_DIAL_STATES = [...AUTONOMY_DIALS, "custom"];
|
|
5214
|
+
const AutonomyDialStateSchema = z.enum(AUTONOMY_DIAL_STATES);
|
|
5215
|
+
/** The points of validation the dial can hand to the agents (the others are never touched). */
|
|
5216
|
+
const AUTONOMY_POINTS = [
|
|
5217
|
+
"framing",
|
|
5218
|
+
"architecture",
|
|
5219
|
+
"breakdown"
|
|
5220
|
+
];
|
|
4978
5221
|
/**
|
|
4979
|
-
*
|
|
4980
|
-
*
|
|
4981
|
-
*
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
5222
|
+
* - `none`: any value.
|
|
5223
|
+
* - `never_allow`: `ask` or `deny` only (the commands that erase data).
|
|
5224
|
+
* - `locked`: `deny` only (the push to the main branch, the force push, the secrets).
|
|
5225
|
+
*/
|
|
5226
|
+
const RULE_LOCKS = {
|
|
5227
|
+
read_files: "none",
|
|
5228
|
+
edit_code: "none",
|
|
5229
|
+
run_checks: "none",
|
|
5230
|
+
install_dependency: "none",
|
|
5231
|
+
internet: "none",
|
|
5232
|
+
destructive_command: "never_allow",
|
|
5233
|
+
push_main: "locked",
|
|
5234
|
+
force_push: "locked",
|
|
5235
|
+
read_secrets: "locked"
|
|
5236
|
+
};
|
|
5237
|
+
const ThresholdSchema = z.int().min(3).max(50);
|
|
5238
|
+
/** Rule values by action: every action has one. */
|
|
5239
|
+
const RuleTableSchema = z.strictObject({
|
|
5240
|
+
read_files: z.enum(RULE_VALUES),
|
|
5241
|
+
edit_code: z.enum(RULE_VALUES),
|
|
5242
|
+
run_checks: z.enum(RULE_VALUES),
|
|
5243
|
+
install_dependency: z.enum(RULE_VALUES),
|
|
5244
|
+
internet: z.enum(RULE_VALUES),
|
|
5245
|
+
destructive_command: z.enum(RULE_VALUES),
|
|
5246
|
+
push_main: z.enum(RULE_VALUES),
|
|
5247
|
+
force_push: z.enum(RULE_VALUES),
|
|
5248
|
+
read_secrets: z.enum(RULE_VALUES)
|
|
5249
|
+
});
|
|
5250
|
+
/**
|
|
5251
|
+
* What a machine says of the autonomy of a product it serves. `dial`, `mode`, `installs`, `kept`
|
|
5252
|
+
* and `rules` are what the product's `method.yaml` records now. The engine reads these settings on
|
|
5253
|
+
* the main branch: when what is recorded is not in force yet, `in_force` says what is (the notch
|
|
5254
|
+
* and the rules that differ), and the web view does not pretend otherwise.
|
|
5255
|
+
*/
|
|
5256
|
+
const ProductAutonomySchema = z.strictObject({
|
|
5257
|
+
dial: AutonomyDialStateSchema,
|
|
5258
|
+
mode: z.enum(VALIDATION_MODES),
|
|
5259
|
+
installs: z.enum(INSTALL_MODES),
|
|
5260
|
+
kept: z.array(z.enum(VALIDATION_POINTS)).max(VALIDATION_POINTS.length),
|
|
5261
|
+
rules: RuleTableSchema,
|
|
5262
|
+
in_force: z.strictObject({
|
|
5263
|
+
dial: AutonomyDialStateSchema,
|
|
5264
|
+
rules: z.partialRecord(z.enum(RULE_ACTIONS), z.enum(RULE_VALUES))
|
|
5265
|
+
}).optional(),
|
|
5266
|
+
/** Validations without a change after which a notch is proposed. */
|
|
5267
|
+
threshold: ThresholdSchema,
|
|
5268
|
+
/** Per kind of point, when the creator said « not now » to the proposal. */
|
|
5269
|
+
snoozed: z.partialRecord(z.enum(AUTONOMY_POINTS), z.iso.datetime()).optional()
|
|
5270
|
+
});
|
|
5271
|
+
z.strictObject({ dial: AutonomyDialSchema });
|
|
5272
|
+
z.strictObject({
|
|
5273
|
+
action: z.enum(RULE_ACTIONS),
|
|
5274
|
+
value: z.enum(RULE_VALUES)
|
|
4997
5275
|
});
|
|
5276
|
+
z.strictObject({
|
|
5277
|
+
snooze: z.enum(AUTONOMY_POINTS).optional(),
|
|
5278
|
+
threshold: ThresholdSchema.optional()
|
|
5279
|
+
}).refine((params) => params.snooze !== void 0 || params.threshold !== void 0, { message: "a kind of point to put off, or a threshold" });
|
|
4998
5280
|
//#endregion
|
|
4999
5281
|
//#region ../../packages/protocol/dist/question-tool.js
|
|
5000
5282
|
/**
|
|
@@ -5080,7 +5362,7 @@ const ChatProofSchema = z.strictObject({
|
|
|
5080
5362
|
});
|
|
5081
5363
|
/** The creator's words, as quoted: 1 to 4000 characters, never only blanks. */
|
|
5082
5364
|
const QuoteSchema$1 = z.string().max(MAX_QUOTE_LENGTH).trim().min(1);
|
|
5083
|
-
const ShortIdSchema$
|
|
5365
|
+
const ShortIdSchema$3 = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$/, { message: "expected 1 to 32 letters, digits, dots, hyphens or underscores" });
|
|
5084
5366
|
/** A tool session id as a hook gives it: printable, at most 200 characters. */
|
|
5085
5367
|
const ToolSessionIdSchema = z.string().trim().min(1).max(200).regex(/^[\x21-\x7e]+$/, { message: "expected printable characters without spaces" });
|
|
5086
5368
|
/**
|
|
@@ -5098,7 +5380,7 @@ const AnswerCheckpointInputSchema = z.strictObject({
|
|
|
5098
5380
|
agent: AgentIdSchema.max(64),
|
|
5099
5381
|
checkpoint: z.string().trim().toUpperCase().pipe(CheckpointIdSchema),
|
|
5100
5382
|
decision: z.enum(ANSWER_DECISIONS).optional(),
|
|
5101
|
-
option: ShortIdSchema$
|
|
5383
|
+
option: ShortIdSchema$3.optional(),
|
|
5102
5384
|
note: z.string().trim().min(1).max(2e3).optional(),
|
|
5103
5385
|
quote: QuoteSchema$1.optional(),
|
|
5104
5386
|
session: ToolSessionIdSchema.optional(),
|
|
@@ -5159,7 +5441,7 @@ const AnswerCheckpointInputSchema = z.strictObject({
|
|
|
5159
5441
|
const CheckpointAnsweredPayloadSchema = z.strictObject({
|
|
5160
5442
|
checkpoint: CheckpointIdSchema,
|
|
5161
5443
|
decision: z.enum(CHAT_DECISIONS),
|
|
5162
|
-
option: ShortIdSchema$
|
|
5444
|
+
option: ShortIdSchema$3.optional(),
|
|
5163
5445
|
note: z.string().trim().min(1).max(2e3).optional(),
|
|
5164
5446
|
source: z.literal("chat"),
|
|
5165
5447
|
quote: QuoteSchema$1,
|
|
@@ -5184,7 +5466,7 @@ const CheckpointAnsweredPayloadSchema = z.strictObject({
|
|
|
5184
5466
|
const CheckpointAutoAppliedPayloadSchema = z.strictObject({
|
|
5185
5467
|
checkpoint: CheckpointIdSchema,
|
|
5186
5468
|
decision: z.literal("option_chosen"),
|
|
5187
|
-
option: ShortIdSchema$
|
|
5469
|
+
option: ShortIdSchema$3
|
|
5188
5470
|
});
|
|
5189
5471
|
/**
|
|
5190
5472
|
* `payload` of `checkpoint.reopened`: an automatic application cancelled. From the chat, it
|
|
@@ -5216,72 +5498,685 @@ const ANTICIPATED_ANSWER_WINDOW_MS = 18e5;
|
|
|
5216
5498
|
/** How long the hook keeps the creator's messages, and how recent a quoted one must be. */
|
|
5217
5499
|
const CHAT_RETENTION_MS = 864e5;
|
|
5218
5500
|
//#endregion
|
|
5219
|
-
//#region ../../packages/protocol/dist/
|
|
5501
|
+
//#region ../../packages/protocol/dist/device-trust.js
|
|
5220
5502
|
/**
|
|
5221
|
-
*
|
|
5222
|
-
*
|
|
5223
|
-
*
|
|
5224
|
-
*
|
|
5503
|
+
* The devices of trust in the journal. A product's
|
|
5504
|
+
* registry of trusted devices is its journal: `device.paired` says "this key is trusted",
|
|
5505
|
+
* `device.revoked` takes it back, `device.confirmed` ends the provisional state of a device whose
|
|
5506
|
+
* handover is complete, `device.root_certified` records that the first phone recognised the root,
|
|
5507
|
+
* and `trust.dismissed` clears a `SECURITY_ROOT_CHANGED` alert. The chain of authorizations must
|
|
5508
|
+
* lead to the root the bridge pinned locally, outside the repository: never to an event's ULID.
|
|
5509
|
+
*
|
|
5510
|
+
* Generic on purpose: schemas and rules only, no transport, no account, no offer.
|
|
5225
5511
|
*/
|
|
5226
|
-
/** Event
|
|
5227
|
-
const
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
"replaced",
|
|
5235
|
-
"step_skipped",
|
|
5236
|
-
"block_lifted",
|
|
5237
|
-
"worktree_gone",
|
|
5238
|
-
"lapsed"
|
|
5239
|
-
]),
|
|
5240
|
-
/** The checkpoint that replaces it: with `replaced`, and only with it. */
|
|
5241
|
-
ref: CheckpointIdSchema.optional()
|
|
5242
|
-
}).refine((payload) => payload.reason === "replaced" === (payload.ref !== void 0), {
|
|
5243
|
-
message: "a ref goes with replaced, and only with it",
|
|
5244
|
-
path: ["ref"]
|
|
5245
|
-
});
|
|
5246
|
-
//#endregion
|
|
5247
|
-
//#region ../../packages/protocol/dist/checkpoint-decided.js
|
|
5512
|
+
/** Event types of the devices of trust, each at version 1. */
|
|
5513
|
+
const DEVICE_TRUST_EVENT_TYPES = {
|
|
5514
|
+
paired: "device.paired",
|
|
5515
|
+
revoked: "device.revoked",
|
|
5516
|
+
rootCertified: "device.root_certified",
|
|
5517
|
+
confirmed: "device.confirmed",
|
|
5518
|
+
dismissed: "trust.dismissed"
|
|
5519
|
+
};
|
|
5248
5520
|
/**
|
|
5249
|
-
*
|
|
5250
|
-
*
|
|
5251
|
-
*
|
|
5252
|
-
* signed by the machine key), never as the creator: its source and its assurance are `agent`, so
|
|
5253
|
-
* the history, the morning summary and every surface always tell it apart from an answer of the
|
|
5254
|
-
* creator, and the creator can reopen it at any time.
|
|
5521
|
+
* The types the engine writes as a system actor (the bridge, or a creator's terminal for the
|
|
5522
|
+
* first ones): refused in any write by an agent. `trust.dismissed` is the creator's (signed by
|
|
5523
|
+
* the creator's key).
|
|
5255
5524
|
*/
|
|
5256
|
-
|
|
5257
|
-
|
|
5525
|
+
const DEVICE_TRUST_SYSTEM_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
5526
|
+
DEVICE_TRUST_EVENT_TYPES.paired,
|
|
5527
|
+
DEVICE_TRUST_EVENT_TYPES.revoked,
|
|
5528
|
+
DEVICE_TRUST_EVENT_TYPES.rootCertified,
|
|
5529
|
+
DEVICE_TRUST_EVENT_TYPES.confirmed
|
|
5530
|
+
]);
|
|
5531
|
+
new Set(Object.values(DEVICE_TRUST_EVENT_TYPES));
|
|
5532
|
+
/** The kinds of device a `device.paired` registers (the creator's key has its own events). */
|
|
5533
|
+
const PAIRED_DEVICE_KINDS = [
|
|
5534
|
+
"machine",
|
|
5535
|
+
"phone",
|
|
5536
|
+
"browser"
|
|
5537
|
+
];
|
|
5538
|
+
/** `machine:`, `human:`, `phone:` or `browser:` then 16 base64url characters. */
|
|
5539
|
+
const DEVICE_ID_PATTERN = /^(machine|human|phone|browser):[A-Za-z0-9_-]{16}$/;
|
|
5540
|
+
/** The identifier of a device `device.paired` registers. */
|
|
5541
|
+
const PAIRED_DEVICE_ID_PATTERN = /^(machine|phone|browser):[A-Za-z0-9_-]{16}$/;
|
|
5542
|
+
/** The identifier of a bridge. */
|
|
5543
|
+
const BRIDGE_ID_PATTERN = /^machine:[A-Za-z0-9_-]{16}$/;
|
|
5544
|
+
/** The kind of identifier (`machine`, `human`, `phone`, `browser`), or undefined when malformed. */
|
|
5545
|
+
function deviceKindOf(device) {
|
|
5546
|
+
if (!DEVICE_ID_PATTERN.test(device)) return void 0;
|
|
5547
|
+
return device.slice(0, device.indexOf(":"));
|
|
5548
|
+
}
|
|
5549
|
+
/** The algorithms of the linked devices (the `webauthn-*` ones serve the terminal's key only). */
|
|
5550
|
+
const DEVICE_SIG_ALG = "ed25519";
|
|
5551
|
+
const DEVICE_ENC_ALG = "x25519";
|
|
5552
|
+
/** 32 bytes in base64url: 43 characters, the last one carrying 4 bits only. */
|
|
5553
|
+
const PUBLIC_KEY_32 = /^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/;
|
|
5554
|
+
/** 16 random bytes in base64url: 22 characters, the last one carrying 2 bits only. */
|
|
5555
|
+
const RANDOM_16 = /^[A-Za-z0-9_-]{21}[AQgw]$/;
|
|
5556
|
+
/** `sha256:` then the 32-byte digest in base64url (an event's hash). */
|
|
5557
|
+
const TRUST_EVENT_HASH_PATTERN = /^sha256:[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/;
|
|
5558
|
+
const publicKey32 = z.string().regex(PUBLIC_KEY_32).refine(isCanonicalBase64url);
|
|
5559
|
+
const utcDatetime$1 = z.iso.datetime();
|
|
5560
|
+
/** A public key of a device with its algorithm: `{ alg, key }`. */
|
|
5561
|
+
const DeviceSigKeySchema = z.strictObject({
|
|
5562
|
+
alg: z.literal(DEVICE_SIG_ALG),
|
|
5563
|
+
key: publicKey32
|
|
5564
|
+
});
|
|
5565
|
+
const DeviceEncKeySchema = z.strictObject({
|
|
5566
|
+
alg: z.literal(DEVICE_ENC_ALG),
|
|
5567
|
+
key: publicKey32
|
|
5568
|
+
});
|
|
5258
5569
|
/**
|
|
5259
|
-
*
|
|
5260
|
-
*
|
|
5261
|
-
*
|
|
5570
|
+
* Every signed structure of trust carries its label `t` (type and version), and a key signs the
|
|
5571
|
+
* types it knows only: an authorization is never taken for an order, nor a relay's challenge for
|
|
5572
|
+
* an authorization. The domain separation stops a signature made for one purpose from
|
|
5573
|
+
* serving another.
|
|
5262
5574
|
*/
|
|
5263
|
-
const
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5575
|
+
const TRUST_SIGNATURE_TAGS = {
|
|
5576
|
+
devicePaired: "device.paired.auth/v1",
|
|
5577
|
+
deviceRevoked: "device.revoked.auth/v1",
|
|
5578
|
+
pairOffer: "pair.offer/v1",
|
|
5579
|
+
rootCertified: "device.root_certified.auth/v1",
|
|
5580
|
+
message: "msg.inner/v1",
|
|
5581
|
+
linkWelcome: "link.welcome/v1",
|
|
5582
|
+
vaultBlob: "vault.blob/v1"
|
|
5583
|
+
};
|
|
5584
|
+
/** True for a label this engine knows. */
|
|
5585
|
+
function isTrustSignatureTag(tag) {
|
|
5586
|
+
return Object.values(TRUST_SIGNATURE_TAGS).includes(tag);
|
|
5272
5587
|
}
|
|
5273
5588
|
/**
|
|
5274
|
-
*
|
|
5275
|
-
* `
|
|
5589
|
+
* The order of a trusted device that offered a pairing (`pair.offer`): one rendezvous
|
|
5590
|
+
* `x`, valid until `expires_at`, for one device of `kind`. The bridge writes `device.paired` on
|
|
5591
|
+
* its authority, once.
|
|
5276
5592
|
*/
|
|
5277
|
-
const
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5593
|
+
const PairOfferSchema = z.strictObject({
|
|
5594
|
+
/** The rendezvous: 16 random bytes in base64url, or the 8-symbol address of a short code. */
|
|
5595
|
+
x: z.union([z.string().regex(RANDOM_16).refine(isCanonicalBase64url), z.string().regex(/^[0-9A-HJKMNP-TV-Z]{8}$/)]),
|
|
5596
|
+
expires_at: utcDatetime$1
|
|
5597
|
+
});
|
|
5598
|
+
/**
|
|
5599
|
+
* The proof that the creator wanted this: the signature, by a device already trusted (or by the
|
|
5600
|
+
* root, from the command run on the root machine), of the record of the event (`order` absent) or
|
|
5601
|
+
* of the signed order `pair.offer` (`order` present).
|
|
5602
|
+
*/
|
|
5603
|
+
const AuthorizationSchema = z.strictObject({
|
|
5604
|
+
signer: z.string().regex(DEVICE_ID_PATTERN),
|
|
5605
|
+
alg: z.literal(DEVICE_SIG_ALG),
|
|
5606
|
+
sig: Ed25519SignatureSchema,
|
|
5607
|
+
order: PairOfferSchema.optional()
|
|
5608
|
+
});
|
|
5609
|
+
/** `payload` of `device.paired` v1. */
|
|
5610
|
+
const DevicePairedPayloadSchema = z.strictObject({
|
|
5611
|
+
device: z.string().regex(PAIRED_DEVICE_ID_PATTERN),
|
|
5612
|
+
kind: z.enum(PAIRED_DEVICE_KINDS),
|
|
5613
|
+
sig: DeviceSigKeySchema,
|
|
5614
|
+
enc: DeviceEncKeySchema,
|
|
5615
|
+
/** The bridge of the perimeter: the authorization holds in the journals this bridge writes. */
|
|
5616
|
+
bridge: z.string().regex(BRIDGE_ID_PATTERN),
|
|
5617
|
+
/** True until the handover is complete (`device.confirmed`): the bridge alone may revoke it. */
|
|
5618
|
+
provisional: z.boolean(),
|
|
5619
|
+
issued_at: utcDatetime$1,
|
|
5620
|
+
authorization: AuthorizationSchema
|
|
5621
|
+
}).refine((payload) => payload.device.startsWith(`${payload.kind}:`), {
|
|
5622
|
+
path: ["kind"],
|
|
5623
|
+
message: "the kind is the prefix of the device"
|
|
5624
|
+
});
|
|
5625
|
+
/** `payload` of `device.revoked` v1. */
|
|
5626
|
+
const DeviceRevokedPayloadSchema = z.strictObject({
|
|
5627
|
+
device: z.string().regex(PAIRED_DEVICE_ID_PATTERN),
|
|
5628
|
+
/**
|
|
5629
|
+
* For a machine that writes to the journal: the hash of its last event the author knew. The
|
|
5630
|
+
* projection accepts of a revoked machine only the ancestors of this head (and of the heads of
|
|
5631
|
+
* the other revocations of it). Null for a device that writes nothing.
|
|
5632
|
+
*/
|
|
5633
|
+
chain_head: z.string().regex(TRUST_EVENT_HASH_PATTERN).nullable(),
|
|
5634
|
+
issued_at: utcDatetime$1,
|
|
5635
|
+
author: z.string().regex(DEVICE_ID_PATTERN),
|
|
5636
|
+
authorization: z.strictObject({
|
|
5637
|
+
alg: z.literal(DEVICE_SIG_ALG),
|
|
5638
|
+
sig: Ed25519SignatureSchema
|
|
5639
|
+
})
|
|
5640
|
+
}).refine((payload) => payload.chain_head === null || payload.device.startsWith("machine:"), {
|
|
5641
|
+
path: ["chain_head"],
|
|
5642
|
+
message: "only a machine has a chain head"
|
|
5643
|
+
});
|
|
5644
|
+
/** `payload` of `device.root_certified` v1: the first phone recognises the root. */
|
|
5645
|
+
const DeviceRootCertifiedPayloadSchema = z.strictObject({
|
|
5646
|
+
root: z.strictObject({
|
|
5647
|
+
device: z.string().regex(DEVICE_ID_PATTERN),
|
|
5648
|
+
key: publicKey32
|
|
5649
|
+
}),
|
|
5650
|
+
issued_at: utcDatetime$1,
|
|
5651
|
+
authorization: AuthorizationSchema
|
|
5652
|
+
});
|
|
5653
|
+
/** `payload` of `device.confirmed` v1: the handover of the device is complete. */
|
|
5654
|
+
const DeviceConfirmedPayloadSchema = z.strictObject({
|
|
5655
|
+
device: z.string().regex(PAIRED_DEVICE_ID_PATTERN),
|
|
5656
|
+
issued_at: utcDatetime$1
|
|
5657
|
+
});
|
|
5658
|
+
/** `payload` of `trust.dismissed` v1: the events (their hashes) the creator dismisses. */
|
|
5659
|
+
const TrustDismissedPayloadSchema = z.strictObject({
|
|
5660
|
+
events: z.array(z.string().regex(TRUST_EVENT_HASH_PATTERN)).min(1).max(16),
|
|
5661
|
+
issued_at: utcDatetime$1
|
|
5662
|
+
});
|
|
5663
|
+
/** The pinned root of a product, kept locally outside the repository (written by `pair`). */
|
|
5664
|
+
const RootPinSchema = z.strictObject({
|
|
5665
|
+
device: z.string().regex(/^(machine|human):[A-Za-z0-9_-]{16}$/),
|
|
5666
|
+
public_key: publicKey32
|
|
5667
|
+
});
|
|
5668
|
+
/**
|
|
5669
|
+
* What the authorization of a `device.paired` signs: its payload without `authorization`, plus the
|
|
5670
|
+
* label. Independent of the product and of `prev`: the bridge writes it in each journal it serves,
|
|
5671
|
+
* but cannot make one without the signer.
|
|
5672
|
+
*/
|
|
5673
|
+
function devicePairedRecord(payload) {
|
|
5674
|
+
return {
|
|
5675
|
+
t: TRUST_SIGNATURE_TAGS.devicePaired,
|
|
5676
|
+
device: payload.device,
|
|
5677
|
+
kind: payload.kind,
|
|
5678
|
+
sig: payload.sig,
|
|
5679
|
+
enc: payload.enc,
|
|
5680
|
+
bridge: payload.bridge,
|
|
5681
|
+
provisional: payload.provisional,
|
|
5682
|
+
issued_at: payload.issued_at
|
|
5683
|
+
};
|
|
5684
|
+
}
|
|
5685
|
+
/** What the signed order `pair.offer` covers. */
|
|
5686
|
+
function pairOfferRecord(input) {
|
|
5687
|
+
return {
|
|
5688
|
+
t: TRUST_SIGNATURE_TAGS.pairOffer,
|
|
5689
|
+
kind: input.kind,
|
|
5690
|
+
bridge: input.bridge,
|
|
5691
|
+
x: input.order.x,
|
|
5692
|
+
expires_at: input.order.expires_at
|
|
5693
|
+
};
|
|
5694
|
+
}
|
|
5695
|
+
/** What the authorization of a `device.revoked` signs. */
|
|
5696
|
+
function deviceRevokedRecord(payload) {
|
|
5697
|
+
return {
|
|
5698
|
+
t: TRUST_SIGNATURE_TAGS.deviceRevoked,
|
|
5699
|
+
device: payload.device,
|
|
5700
|
+
chain_head: payload.chain_head,
|
|
5701
|
+
issued_at: payload.issued_at,
|
|
5702
|
+
author: payload.author
|
|
5703
|
+
};
|
|
5704
|
+
}
|
|
5705
|
+
/** What the authorization of a `device.root_certified` signs. */
|
|
5706
|
+
function rootCertifiedRecord(payload) {
|
|
5707
|
+
return {
|
|
5708
|
+
t: TRUST_SIGNATURE_TAGS.rootCertified,
|
|
5709
|
+
root: payload.root,
|
|
5710
|
+
issued_at: payload.issued_at
|
|
5711
|
+
};
|
|
5712
|
+
}
|
|
5713
|
+
//#endregion
|
|
5714
|
+
//#region ../../packages/protocol/dist/checkpoint-web.js
|
|
5715
|
+
/**
|
|
5716
|
+
* Event types of the answers given from the web view, at version 1. A reversible point's answer
|
|
5717
|
+
* is first held (`held`) for the length of the undo window, with the point still open for everyone;
|
|
5718
|
+
* then it is either taken back (`holdCancelled`) or carried out (`answered`). An answer to a point
|
|
5719
|
+
* that cannot be undone is carried out at once. A held answer whose carrying out the machine could
|
|
5720
|
+
* not do (the merge of a story that the forge refuses) is released (`holdFailed`): the point stays
|
|
5721
|
+
* open and says why.
|
|
5722
|
+
*/
|
|
5723
|
+
const WEB_ANSWER_EVENT_TYPES = {
|
|
5724
|
+
answered: "checkpoint.web_answered",
|
|
5725
|
+
held: "checkpoint.web_held",
|
|
5726
|
+
holdCancelled: "checkpoint.web_hold_cancelled",
|
|
5727
|
+
holdFailed: "checkpoint.web_hold_failed"
|
|
5728
|
+
};
|
|
5729
|
+
/** How long the machine holds a reversible answer before it carries it out: the creator may undo it. */
|
|
5730
|
+
const WEB_ANSWER_WINDOW_MS = 1e4;
|
|
5731
|
+
/**
|
|
5732
|
+
* How long after its window a held answer is still waited for. Past it, the machine did not carry
|
|
5733
|
+
* the answer out (it is off, or the journal refuses it): the point is open again for a new answer.
|
|
5734
|
+
*/
|
|
5735
|
+
const WEB_ANSWER_STALE_MS = 15e3;
|
|
5736
|
+
/** The longest note the creator types with a decision, like a resolution's note. */
|
|
5737
|
+
const MAX_WEB_ANSWER_NOTE_LENGTH = 2e3;
|
|
5738
|
+
/** Where an answer carried by a signed order came from: a browser (`web`) or a phone (`mobile`). */
|
|
5739
|
+
const ANSWER_SOURCES = ["web", "mobile"];
|
|
5740
|
+
/**
|
|
5741
|
+
* The source of an answer given by `device`, drawn from the kind of its identifier (a browser's
|
|
5742
|
+
* `browser:`, a phone's `phone:`), which the journal registered when the device was paired. Undefined
|
|
5743
|
+
* for any other device: a machine never answers this way.
|
|
5744
|
+
*/
|
|
5745
|
+
function answerSourceOf(device) {
|
|
5746
|
+
if (device.startsWith("browser:")) return "web";
|
|
5747
|
+
if (device.startsWith("phone:")) return "mobile";
|
|
5748
|
+
}
|
|
5749
|
+
const ShortIdSchema$2 = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$/, { message: "expected 1 to 32 letters, digits, dots, hyphens or underscores" });
|
|
5750
|
+
function checkAnswer(value, context) {
|
|
5751
|
+
if (value.decision === "option_chosen" !== (value.option !== void 0)) context.addIssue({
|
|
5752
|
+
code: "custom",
|
|
5753
|
+
path: ["option"],
|
|
5754
|
+
message: "an option goes with option_chosen, and only with it"
|
|
5755
|
+
});
|
|
5756
|
+
if (NOTE_REQUIRED.has(value.decision) && value.note === void 0) context.addIssue({
|
|
5757
|
+
code: "custom",
|
|
5758
|
+
path: ["note"],
|
|
5759
|
+
message: "this decision needs a note"
|
|
5760
|
+
});
|
|
5761
|
+
}
|
|
5762
|
+
z.strictObject({
|
|
5763
|
+
checkpoint: CheckpointIdSchema,
|
|
5764
|
+
decision: z.enum(CHAT_DECISIONS),
|
|
5765
|
+
option: ShortIdSchema$2.optional(),
|
|
5766
|
+
note: z.string().trim().min(1).max(MAX_WEB_ANSWER_NOTE_LENGTH).optional()
|
|
5767
|
+
}).superRefine(checkAnswer);
|
|
5768
|
+
/** Refuses a source that is not the one the device's kind gives. */
|
|
5769
|
+
function checkSource(value, context) {
|
|
5770
|
+
if (answerSourceOf(value.device) !== value.source) context.addIssue({
|
|
5771
|
+
code: "custom",
|
|
5772
|
+
path: ["source"],
|
|
5773
|
+
message: "the source is the one the device's kind gives"
|
|
5774
|
+
});
|
|
5775
|
+
}
|
|
5776
|
+
/**
|
|
5777
|
+
* `payload` of `checkpoint.web_answered`: the point, the decision, the order that carried it and
|
|
5778
|
+
* the browser or phone device that signed it. The assurance (`device`) is drawn from the event's
|
|
5779
|
+
* type by the projection, never declared; the source must be the one the device's kind gives.
|
|
5780
|
+
*/
|
|
5781
|
+
const CheckpointWebAnsweredPayloadSchema = z.strictObject({
|
|
5782
|
+
checkpoint: CheckpointIdSchema,
|
|
5783
|
+
decision: z.enum(CHAT_DECISIONS),
|
|
5784
|
+
option: ShortIdSchema$2.optional(),
|
|
5785
|
+
note: z.string().trim().min(1).max(MAX_WEB_ANSWER_NOTE_LENGTH).optional(),
|
|
5786
|
+
source: z.enum(ANSWER_SOURCES),
|
|
5787
|
+
order: UlidSchema,
|
|
5788
|
+
device: z.string().regex(PAIRED_DEVICE_ID_PATTERN)
|
|
5789
|
+
}).superRefine(checkAnswer).superRefine(checkSource);
|
|
5790
|
+
z.strictObject({ checkpoint: CheckpointIdSchema });
|
|
5791
|
+
/**
|
|
5792
|
+
* `payload` of `checkpoint.web_held`: an answer the machine holds for `WEB_ANSWER_WINDOW_MS`. It
|
|
5793
|
+
* is the answer of `checkpoint.web_answered`, which the machine writes when the window is over; the
|
|
5794
|
+
* point stays open until then.
|
|
5795
|
+
*/
|
|
5796
|
+
const CheckpointWebHeldPayloadSchema = CheckpointWebAnsweredPayloadSchema;
|
|
5797
|
+
/**
|
|
5798
|
+
* `payload` of `checkpoint.web_hold_cancelled`: the held answer of the order `held` taken back by
|
|
5799
|
+
* the order `order`, signed by the browser device `device`. The point was never answered.
|
|
5800
|
+
*/
|
|
5801
|
+
const CheckpointWebHoldCancelledPayloadSchema = z.strictObject({
|
|
5802
|
+
checkpoint: CheckpointIdSchema,
|
|
5803
|
+
held: UlidSchema,
|
|
5804
|
+
order: UlidSchema,
|
|
5805
|
+
device: z.string().regex(PAIRED_DEVICE_ID_PATTERN)
|
|
5806
|
+
});
|
|
5807
|
+
/** The longest code and reason a failed carrying out records: short tokens, never a message. */
|
|
5808
|
+
const FAILURE_TOKEN = /^[A-Za-z][A-Za-z0-9_.]{0,47}$/;
|
|
5809
|
+
/**
|
|
5810
|
+
* `payload` of `checkpoint.web_hold_failed`: the held answer of the order `held` that the machine
|
|
5811
|
+
* could not carry out (`code` is the engine's error code, `reason` its short reason when it has
|
|
5812
|
+
* one, `merge_failed` and `head_moved` for instance). Nothing was decided and the point is open
|
|
5813
|
+
* again. Neither a message nor an address nor a token is recorded.
|
|
5814
|
+
*/
|
|
5815
|
+
const CheckpointWebHoldFailedPayloadSchema = z.strictObject({
|
|
5816
|
+
checkpoint: CheckpointIdSchema,
|
|
5817
|
+
held: UlidSchema,
|
|
5818
|
+
device: z.string().regex(PAIRED_DEVICE_ID_PATTERN),
|
|
5819
|
+
code: z.string().regex(FAILURE_TOKEN),
|
|
5820
|
+
reason: z.string().regex(FAILURE_TOKEN).optional()
|
|
5821
|
+
});
|
|
5822
|
+
//#endregion
|
|
5823
|
+
//#region ../../packages/protocol/dist/push.js
|
|
5824
|
+
/**
|
|
5825
|
+
* Notifications without content. A notification carries an opaque identifier, a category and a
|
|
5826
|
+
* level of urgency, and nothing else: the relay never learns a product's name, a title or a text.
|
|
5827
|
+
* The machine decides what is urgent (it knows the content); the relay only forwards, to the
|
|
5828
|
+
* phones of the account, through the push services of the platforms.
|
|
5829
|
+
*/
|
|
5830
|
+
/** The platforms a phone can be reached on: APNs (iOS) and FCM (Android), directly. */
|
|
5831
|
+
const PUSH_PLATFORMS = ["ios", "android"];
|
|
5832
|
+
/** What a notification is about, in the words of the product (never a product's own text). */
|
|
5833
|
+
const NOTIFY_CATEGORIES = [
|
|
5834
|
+
"decision",
|
|
5835
|
+
"permission",
|
|
5836
|
+
"merge"
|
|
5837
|
+
];
|
|
5838
|
+
/** How hard a notification interrupts. */
|
|
5839
|
+
const NOTIFY_URGENCIES = ["urgent", "normal"];
|
|
5840
|
+
const PushTokenSchema = z.string().min(16).max(4096).regex(/^[\x21-\x7e]+$/);
|
|
5841
|
+
/** The opaque identifier of a notification: 43 characters of base64url (a digest). */
|
|
5842
|
+
const NotifyIdSchema = z.string().regex(/^[A-Za-z0-9_-]{43}$/);
|
|
5843
|
+
/**
|
|
5844
|
+
* What a notification carries, in total. The relay builds the platform message from this and from
|
|
5845
|
+
* a fixed text of its own; nothing of the product is in it.
|
|
5846
|
+
*/
|
|
5847
|
+
const PushNoticeSchema = z.strictObject({
|
|
5848
|
+
id: NotifyIdSchema,
|
|
5849
|
+
category: z.enum(NOTIFY_CATEGORIES),
|
|
5850
|
+
urgency: z.enum(NOTIFY_URGENCIES)
|
|
5851
|
+
});
|
|
5852
|
+
/** The surfaces of the creator that can be notified (the watch comes with its own app). */
|
|
5853
|
+
const NOTIFY_SURFACES = ["web", "mobile"];
|
|
5854
|
+
/** What a surface rings for: only the urgent, everything that comes back to the creator, nothing. */
|
|
5855
|
+
const SURFACE_CHOICES = [
|
|
5856
|
+
"urgent",
|
|
5857
|
+
"all",
|
|
5858
|
+
"none"
|
|
5859
|
+
];
|
|
5860
|
+
/** A time zone name as the platforms give it (`Europe/Paris`): bounded, no free text. */
|
|
5861
|
+
const TimeZoneSchema = z.string().regex(/^[A-Za-z][A-Za-z0-9_+\-/]{0,63}$/);
|
|
5862
|
+
const CategoriesSchema = z.array(z.enum(NOTIFY_CATEGORIES)).max(NOTIFY_CATEGORIES.length).refine((list) => new Set(list).size === list.length, { message: "duplicate category" });
|
|
5863
|
+
const MinuteOfDaySchema = z.number().int().min(0).max(1439);
|
|
5864
|
+
/**
|
|
5865
|
+
* What the creator chose about notifications. It holds no product text and no content: categories,
|
|
5866
|
+
* hours and surfaces. The relay keeps it in clear because it applies it (it never sees more than an
|
|
5867
|
+
* opaque notice); the same document is shown and changed from every surface.
|
|
5868
|
+
*/
|
|
5869
|
+
const NotifyPrefsSchema = z.strictObject({
|
|
5870
|
+
/** The categories that notify at all. */
|
|
5871
|
+
types: CategoriesSchema,
|
|
5872
|
+
/** Active on one surface: the others stay quiet. Off: each surface follows its own choice. */
|
|
5873
|
+
presence: z.boolean(),
|
|
5874
|
+
surfaces: z.strictObject({
|
|
5875
|
+
web: z.enum(SURFACE_CHOICES),
|
|
5876
|
+
mobile: z.enum(SURFACE_CHOICES)
|
|
5877
|
+
}),
|
|
5878
|
+
quiet: z.strictObject({
|
|
5879
|
+
enabled: z.boolean(),
|
|
5880
|
+
/** Minute of the day the silence starts and ends, in `tz`. Equal values mean no silence. */
|
|
5881
|
+
from: MinuteOfDaySchema,
|
|
5882
|
+
to: MinuteOfDaySchema,
|
|
5883
|
+
tz: TimeZoneSchema,
|
|
5884
|
+
/** The urgent categories that still ring during the silence: none by default. */
|
|
5885
|
+
pass: CategoriesSchema
|
|
5886
|
+
})
|
|
5887
|
+
});
|
|
5888
|
+
[...NOTIFY_CATEGORIES];
|
|
5889
|
+
/** The two briefings: the morning one (what waits) and the evening one (the day, then the calm decisions). */
|
|
5890
|
+
const BRIEFING_PARTS = ["morning", "evening"];
|
|
5891
|
+
/** The request shape (without the identifier, the version and the product, which the file reading adds). */
|
|
5892
|
+
const BriefingRequestFields = {
|
|
5893
|
+
op: z.literal("briefing"),
|
|
5894
|
+
/** Absent: the morning briefing. */
|
|
5895
|
+
part: z.enum(BRIEFING_PARTS).optional()
|
|
5896
|
+
};
|
|
5897
|
+
const Count$2 = z.int().min(0).max(Number.MAX_SAFE_INTEGER);
|
|
5898
|
+
/** What the creator can do with a point from a phone: every decision, none to approve, or none at all. */
|
|
5899
|
+
const BRIEFING_ANSWERING = [
|
|
5900
|
+
"full",
|
|
5901
|
+
"no_approval",
|
|
5902
|
+
"elsewhere"
|
|
5903
|
+
];
|
|
5904
|
+
const BriefingOptionSchema = z.strictObject({
|
|
5905
|
+
id: z.string().min(1).max(64),
|
|
5906
|
+
label: z.string().min(1).max(100),
|
|
5907
|
+
recommended: z.boolean()
|
|
5908
|
+
});
|
|
5909
|
+
/**
|
|
5910
|
+
* What the listening version of a point is made from: the fields its agent already wrote when it
|
|
5911
|
+
* opened the point (the card's effect, its first risk and its reason, a deliverable's short
|
|
5912
|
+
* version), as plain text, or the start of the summary when no card was written. The device turns
|
|
5913
|
+
* them into sentences of its own catalog; the machine never writes a sentence, and nothing is
|
|
5914
|
+
* asked of a model. Every field is untrusted text written by an agent.
|
|
5915
|
+
*/
|
|
5916
|
+
const BriefingListenSchema = z.strictObject({
|
|
5917
|
+
/** What the point changes for the people who use the product (the card's `effect`). */
|
|
5918
|
+
effect: z.string().min(1).max(300).optional(),
|
|
5919
|
+
/** The start of the summary: only when the agent wrote no card. */
|
|
5920
|
+
summary: z.string().min(1).max(600).optional(),
|
|
5921
|
+
/** The first risk of the card. */
|
|
5922
|
+
risk: z.string().min(1).max(200).optional(),
|
|
5923
|
+
/** Why the agent proposes this, and the alternative it set aside. */
|
|
5924
|
+
reason: z.string().min(1).max(200).optional(),
|
|
5925
|
+
rejected: z.string().min(1).max(200).optional(),
|
|
5926
|
+
/** A deliverable's short version, written by its agent for about fifteen seconds of reading. */
|
|
5927
|
+
short: z.string().min(1).max(600).optional()
|
|
5928
|
+
});
|
|
5929
|
+
const BriefingItemSchema = z.strictObject({
|
|
5930
|
+
/** The checkpoint's identifier: the device finds the point on its own board with it. */
|
|
5931
|
+
id: z.string().min(1).max(64),
|
|
5932
|
+
type: z.string().min(1).max(32),
|
|
5933
|
+
risk: z.enum([
|
|
5934
|
+
"low",
|
|
5935
|
+
"medium",
|
|
5936
|
+
"high"
|
|
5937
|
+
]),
|
|
5938
|
+
/** True when the point holds something back or sits at a high risk (the rule of the notifications). */
|
|
5939
|
+
urgent: z.boolean(),
|
|
5940
|
+
/** True when the point cannot be taken back once decided. */
|
|
5941
|
+
irreversible: z.boolean(),
|
|
5942
|
+
answering: z.enum(BRIEFING_ANSWERING),
|
|
5943
|
+
/** The agent that opened the point, when one did. */
|
|
5944
|
+
agent: z.string().min(1).max(64).optional(),
|
|
5945
|
+
title: z.string().min(1).max(120),
|
|
5946
|
+
story: z.string().min(1).max(32).optional(),
|
|
5947
|
+
options: z.array(BriefingOptionSchema).max(6),
|
|
5948
|
+
/** The fields the listening version is made from; absent when the point has no text to read. */
|
|
5949
|
+
listen: BriefingListenSchema.optional(),
|
|
5950
|
+
/** Set on a story's merge point: the story and the branch it lands on. */
|
|
5951
|
+
merge: z.strictObject({
|
|
5952
|
+
story: z.string().min(1).max(32),
|
|
5953
|
+
base: z.string().min(1).max(100)
|
|
5954
|
+
}).optional()
|
|
5955
|
+
});
|
|
5956
|
+
/**
|
|
5957
|
+
* The day of a product, in the evening briefing: what was delivered since midnight on the machine's
|
|
5958
|
+
* clock, how many points still wait at a high risk or holding something back (they are counted,
|
|
5959
|
+
* never listed: they are not for the evening), and what the day cost when the bridge could tell.
|
|
5960
|
+
*/
|
|
5961
|
+
const BriefingDaySchema = z.strictObject({
|
|
5962
|
+
/** Stories merged today. */
|
|
5963
|
+
merged: Count$2,
|
|
5964
|
+
/** Publications the creator validated today. */
|
|
5965
|
+
published: Count$2,
|
|
5966
|
+
/** Decisions the creator made today. */
|
|
5967
|
+
decided: Count$2,
|
|
5968
|
+
/** Points that wait and are urgent: not in `items`. */
|
|
5969
|
+
urgent: Count$2,
|
|
5970
|
+
/** What the measured sessions of the day cost; absent when the costs cannot be read. */
|
|
5971
|
+
cost: z.strictObject({
|
|
5972
|
+
/** Dollars of the models the table prices; absent when no model of the day has a price. */
|
|
5973
|
+
usd: z.number().min(0).max(1e8).optional(),
|
|
5974
|
+
/** `real`, `partial` or `unknown`: a device shows another value as `unknown`. */
|
|
5975
|
+
nature: z.string().min(1).max(16),
|
|
5976
|
+
tokens: Count$2
|
|
5977
|
+
}).optional()
|
|
5978
|
+
});
|
|
5979
|
+
const BriefingDataSchema = z.strictObject({
|
|
5980
|
+
op: z.literal("briefing"),
|
|
5981
|
+
/** Which briefing this is; absent in a morning briefing. */
|
|
5982
|
+
part: z.enum(BRIEFING_PARTS).optional(),
|
|
5983
|
+
/** The day of the product; present in an evening briefing only. */
|
|
5984
|
+
day: BriefingDaySchema.optional(),
|
|
5985
|
+
/** Milliseconds since the epoch, on the machine's clock. */
|
|
5986
|
+
composed_at: Count$2,
|
|
5987
|
+
/** What moved in the last 24 hours (in the evening briefing, since midnight). */
|
|
5988
|
+
progress: z.strictObject({
|
|
5989
|
+
merged: Count$2,
|
|
5990
|
+
decided: Count$2
|
|
5991
|
+
}),
|
|
5992
|
+
/** How many points wait for the creator (held answers excluded), whatever `items` lists. */
|
|
5993
|
+
waiting: Count$2,
|
|
5994
|
+
items: z.array(BriefingItemSchema).max(12)
|
|
5995
|
+
});
|
|
5996
|
+
//#endregion
|
|
5997
|
+
//#region ../../packages/protocol/dist/budget.js
|
|
5998
|
+
/**
|
|
5999
|
+
* Budgets: each story's ceiling comes from the journal (the last sprint approved that
|
|
6000
|
+
* holds it, or the creator's answer to a budget checkpoint); its cost from the agents' declared
|
|
6001
|
+
* costs (`cost.reported`, encrypted). When a declared cost reaches the alert ratio, or goes over
|
|
6002
|
+
* the ceiling, `core` writes `budget.threshold_reached` (no amount of cost in it), right after
|
|
6003
|
+
* that cost in the same write. Over the ceiling, with `on_exceed: block`, it opens a budget
|
|
6004
|
+
* checkpoint in between, and the story's write tools answer `BUDGET_EXCEEDED` until the creator
|
|
6005
|
+
* answers it.
|
|
6006
|
+
*/
|
|
6007
|
+
/** Event types of budgets, each at version 1. */
|
|
6008
|
+
const BUDGET_EVENT_TYPES = { thresholdReached: "budget.threshold_reached" };
|
|
6009
|
+
/** The thresholds a story's cost may reach: the alert ratio, then the ceiling itself. */
|
|
6010
|
+
const BUDGET_LEVELS = ["alert", "exceeded"];
|
|
6011
|
+
/** The largest ceiling, in US dollars (raising a ceiling stops there). */
|
|
6012
|
+
const MAX_BUDGET_USD = 1e9;
|
|
6013
|
+
/**
|
|
6014
|
+
* The options of a budget checkpoint: raise the ceiling by half (at least to $1), double it (at
|
|
6015
|
+
* least to $2), go on without a ceiling, or stop the story. A raise that would change nothing (at
|
|
6016
|
+
* `MAX_BUDGET_USD`) is never offered.
|
|
6017
|
+
*/
|
|
6018
|
+
const BUDGET_OPTIONS = [
|
|
6019
|
+
"raise-50",
|
|
6020
|
+
"raise-100",
|
|
6021
|
+
"no-limit",
|
|
6022
|
+
"stop"
|
|
6023
|
+
];
|
|
6024
|
+
/** The option a budget checkpoint recommends when it offers it (else `stop`). */
|
|
6025
|
+
const BUDGET_RECOMMENDATION = "raise-50";
|
|
6026
|
+
/** The prefix of a budget checkpoint's title, before the story. */
|
|
6027
|
+
const BUDGET_TITLE_PREFIX = "Budget exceeded: ";
|
|
6028
|
+
/**
|
|
6029
|
+
* The source of a ceiling: the sprint (`S-…`) whose approval set it, or the budget checkpoint
|
|
6030
|
+
* (`G-…`) whose answer did. A threshold counts only for the ceiling it names.
|
|
6031
|
+
*/
|
|
6032
|
+
const CeilingIdSchema = z.string().regex(/^[SG]-[0-9A-HJKMNP-TV-Z]{10}$/, { message: "expected the id of a sprint (S-…) or of a checkpoint (G-…)" });
|
|
6033
|
+
/**
|
|
6034
|
+
* `payload` of `budget.threshold_reached`: the level reached, the ceiling it was measured against
|
|
6035
|
+
* (its amount and its source, never the cost), the settings in force, and the budget checkpoint
|
|
6036
|
+
* opened just before it in the same write (`exceeded` under `block` only; absent when one is
|
|
6037
|
+
* already open).
|
|
6038
|
+
*/
|
|
6039
|
+
const BudgetThresholdReachedPayloadSchema = z.strictObject({
|
|
6040
|
+
level: z.enum(BUDGET_LEVELS),
|
|
6041
|
+
budget_usd: z.number().finite().min(0).max(MAX_BUDGET_USD),
|
|
6042
|
+
ceiling: CeilingIdSchema,
|
|
6043
|
+
alert_ratio: z.number().finite().min(MIN_ALERT_RATIO).max(1),
|
|
6044
|
+
on_exceed: z.enum(BUDGET_EXCEED_MODES),
|
|
6045
|
+
checkpoint: CheckpointIdSchema.optional()
|
|
6046
|
+
}).superRefine((value, context) => {
|
|
6047
|
+
if (value.checkpoint !== void 0 && (value.level !== "exceeded" || value.on_exceed !== "block")) context.addIssue({
|
|
6048
|
+
code: "custom",
|
|
6049
|
+
path: ["checkpoint"],
|
|
6050
|
+
message: "a checkpoint goes only with an exceeded level under block"
|
|
6051
|
+
});
|
|
6052
|
+
});
|
|
6053
|
+
//#endregion
|
|
6054
|
+
//#region ../../packages/protocol/dist/call-guard.js
|
|
6055
|
+
/**
|
|
6056
|
+
* The guard before every tool call. A hook of the AI tool runs before each tool (`check_call`);
|
|
6057
|
+
* the engine refuses the call when the story it belongs to reached its budget, or when the team is
|
|
6058
|
+
* paused and the call would write, run a command or start an agent, and lets it through otherwise.
|
|
6059
|
+
* Values come as the hook substitutes them (`${tool_name}`): possibly empty, or left as `${…}` when
|
|
6060
|
+
* the hook's input has no such field; the engine reads them with that doubt. In doubt the call goes
|
|
6061
|
+
* through.
|
|
6062
|
+
*/
|
|
6063
|
+
const CheckCallInputSchema = z.strictObject({
|
|
6064
|
+
/** The tool about to run (`Bash`, `Edit`, `mcp__<server>__<tool>`…). */
|
|
6065
|
+
tool: z.string().max(200),
|
|
6066
|
+
session: z.string().trim().min(1).max(200).optional(),
|
|
6067
|
+
cwd: z.string().max(2e3).optional(),
|
|
6068
|
+
/** The kind of sub-agent that makes the call, when the tool says it (absent for the main session). */
|
|
6069
|
+
agent: z.string().max(200).optional()
|
|
6070
|
+
});
|
|
6071
|
+
z.record(z.string(), z.unknown());
|
|
6072
|
+
/** The event Noah writes when he takes a message of the creator. */
|
|
6073
|
+
const CHAIN_EVENT_TYPES = { instructionGiven: "agent.instruction_given" };
|
|
6074
|
+
/** The longest message, in characters. */
|
|
6075
|
+
const CHAIN_INSTRUCTION_MAX_CHARS = 4e3;
|
|
6076
|
+
const AgentTargetSchema = AgentIdSchema.max(64);
|
|
6077
|
+
/**
|
|
6078
|
+
* Fields that devices older than this version still send with a pause or a resumption. They are
|
|
6079
|
+
* read and ignored, so that an older phone keeps its buttons; no view sends them any more.
|
|
6080
|
+
*/
|
|
6081
|
+
const IGNORED_OLD_FIELDS = {
|
|
6082
|
+
when: z.enum(["now", "after_step"]).optional(),
|
|
6083
|
+
hand: z.enum(["chat", "terminal"]).optional(),
|
|
6084
|
+
close_session: z.boolean().optional()
|
|
6085
|
+
};
|
|
6086
|
+
z.strictObject({
|
|
6087
|
+
agent: AgentTargetSchema.optional(),
|
|
6088
|
+
...IGNORED_OLD_FIELDS
|
|
6089
|
+
});
|
|
6090
|
+
z.strictObject({
|
|
6091
|
+
agent: AgentTargetSchema.optional(),
|
|
6092
|
+
...IGNORED_OLD_FIELDS
|
|
6093
|
+
});
|
|
6094
|
+
z.strictObject({ agent: AgentTargetSchema.optional() });
|
|
6095
|
+
z.strictObject({
|
|
6096
|
+
text: z.string().min(1).max(CHAIN_INSTRUCTION_MAX_CHARS).refine((value) => value.trim().length > 0, { message: "the message is empty" }),
|
|
6097
|
+
input: z.enum(["text", "voice"]).optional()
|
|
6098
|
+
});
|
|
6099
|
+
/** `payload` of `agent.instruction_given`: the creator's message, kept encrypted in the journal. */
|
|
6100
|
+
const InstructionGivenPayloadSchema = z.strictObject({
|
|
6101
|
+
/** Who the creator spoke to. */
|
|
6102
|
+
to: AgentTargetSchema,
|
|
6103
|
+
/** The agent the message went to when Noah routed it (events of earlier versions). */
|
|
6104
|
+
routed_to: AgentTargetSchema.optional(),
|
|
6105
|
+
text: z.string().min(1).max(CHAIN_INSTRUCTION_MAX_CHARS),
|
|
6106
|
+
/** How the message was delivered (events of earlier versions use `now` and `queued`). */
|
|
6107
|
+
delivery: z.enum([
|
|
6108
|
+
"now",
|
|
6109
|
+
"queued",
|
|
6110
|
+
"chat"
|
|
6111
|
+
])
|
|
6112
|
+
});
|
|
6113
|
+
//#endregion
|
|
6114
|
+
//#region ../../packages/protocol/dist/checkpoint-closed.js
|
|
6115
|
+
/**
|
|
6116
|
+
* A point closed by the engine itself: when what a checkpoint asks about is already
|
|
6117
|
+
* settled, nobody has to answer it. The engine writes `checkpoint.closed` with the reason, and the
|
|
6118
|
+
* projection checks the reason against the journal (a point that is still waiting for something is
|
|
6119
|
+
* never closed). The creator is never shown a point whose object is settled.
|
|
6120
|
+
*/
|
|
6121
|
+
/** Event type of the closing of a checkpoint by the engine, at version 1. */
|
|
6122
|
+
const CHECKPOINT_CLOSED_EVENT_TYPES = { closed: "checkpoint.closed" };
|
|
6123
|
+
/** `payload` of `checkpoint.closed`: the checkpoint, why, and the newer one for `replaced`. */
|
|
6124
|
+
const CheckpointClosedPayloadSchema = z.strictObject({
|
|
6125
|
+
checkpoint: CheckpointIdSchema,
|
|
6126
|
+
reason: z.enum([
|
|
6127
|
+
"story_merged",
|
|
6128
|
+
"review_redone",
|
|
6129
|
+
"replaced",
|
|
6130
|
+
"step_skipped",
|
|
6131
|
+
"block_lifted",
|
|
6132
|
+
"worktree_gone",
|
|
6133
|
+
"lapsed"
|
|
6134
|
+
]),
|
|
6135
|
+
/** The checkpoint that replaces it: with `replaced`, and only with it. */
|
|
6136
|
+
ref: CheckpointIdSchema.optional()
|
|
6137
|
+
}).refine((payload) => payload.reason === "replaced" === (payload.ref !== void 0), {
|
|
6138
|
+
message: "a ref goes with replaced, and only with it",
|
|
6139
|
+
path: ["ref"]
|
|
6140
|
+
});
|
|
6141
|
+
//#endregion
|
|
6142
|
+
//#region ../../packages/protocol/dist/checkpoint-decided.js
|
|
6143
|
+
/**
|
|
6144
|
+
* A point decided by the agent responsible for it. The creator keeps for
|
|
6145
|
+
* themselves the points they chose (`validation.points` of `method.yaml`); any other point is
|
|
6146
|
+
* decided by an agent, which says why. The decision is written as the agent (`actor.kind: agent`,
|
|
6147
|
+
* signed by the machine key), never as the creator: its source and its assurance are `agent`, so
|
|
6148
|
+
* the history, the morning summary and every surface always tell it apart from an answer of the
|
|
6149
|
+
* creator, and the creator can reopen it at any time.
|
|
6150
|
+
*/
|
|
6151
|
+
/** Event type of a point decided by an agent, at version 1. */
|
|
6152
|
+
const CHECKPOINT_DECIDED_EVENT_TYPES = { decided: "checkpoint.decided" };
|
|
6153
|
+
/**
|
|
6154
|
+
* What `decide_checkpoint` may be given: a decision, or `escalate`, the agent handing the point back
|
|
6155
|
+
* to the creator with its reason when it cannot or should not decide it (a merge the forge refuses
|
|
6156
|
+
* and nobody can fix, a point it finds beyond its remit). The point then waits for the creator.
|
|
6157
|
+
*/
|
|
6158
|
+
const DECIDE_CHOICES = [...CHAT_DECISIONS, "escalate"];
|
|
6159
|
+
const ShortIdSchema$1 = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$/, { message: "expected 1 to 32 letters, digits, dots, hyphens or underscores" });
|
|
6160
|
+
const ReasonSchema$1 = z.string().trim().min(1).max(1e3);
|
|
6161
|
+
function checkOption(value, context) {
|
|
6162
|
+
if (value.decision === "option_chosen" !== (value.option !== void 0)) context.addIssue({
|
|
6163
|
+
code: "custom",
|
|
6164
|
+
path: ["option"],
|
|
6165
|
+
message: "an option goes with option_chosen, and only with it"
|
|
6166
|
+
});
|
|
6167
|
+
}
|
|
6168
|
+
/**
|
|
6169
|
+
* Input of `decide_checkpoint`: the agent that decides, the point, its decision and the reason.
|
|
6170
|
+
* `option` goes with `option_chosen` only.
|
|
6171
|
+
*/
|
|
6172
|
+
const DecideCheckpointInputSchema = z.strictObject({
|
|
6173
|
+
agent: AgentIdSchema.max(64),
|
|
6174
|
+
checkpoint: z.string().trim().toUpperCase().pipe(CheckpointIdSchema),
|
|
6175
|
+
decision: z.enum(DECIDE_CHOICES),
|
|
6176
|
+
option: ShortIdSchema$1.optional(),
|
|
6177
|
+
reason: ReasonSchema$1
|
|
6178
|
+
}).superRefine(checkOption);
|
|
6179
|
+
/** `payload` of `checkpoint.decided`: the point, the decision and the agent's reason. */
|
|
5285
6180
|
const CheckpointDecidedPayloadSchema = z.strictObject({
|
|
5286
6181
|
checkpoint: CheckpointIdSchema,
|
|
5287
6182
|
decision: z.enum(DECIDE_CHOICES),
|
|
@@ -5357,6 +6252,364 @@ const CheckCiInputSchema = z.strictObject({
|
|
|
5357
6252
|
forge: z.enum(CI_FORGES).optional()
|
|
5358
6253
|
});
|
|
5359
6254
|
//#endregion
|
|
6255
|
+
//#region ../../packages/protocol/dist/forge-link.js
|
|
6256
|
+
/**
|
|
6257
|
+
* Linking a code platform (GitHub, GitLab) to a machine. The creator's device asks, with a signed
|
|
6258
|
+
* order, for the machine to be authorised at the platform; the machine does it itself, keeps the
|
|
6259
|
+
* token in its own system keychain and says what became of it in its report. The token is in none
|
|
6260
|
+
* of these shapes: a device only ever learns the state of a link (who, how far, until when) and,
|
|
6261
|
+
* while an authorisation is under way, the short code and the public address to enter it at.
|
|
6262
|
+
*/
|
|
6263
|
+
/** The platforms a machine can be linked to. */
|
|
6264
|
+
const FORGE_PROVIDERS = ["github", "gitlab"];
|
|
6265
|
+
const ForgeHostSchema = z.string().max(50).regex(/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*(?::[1-9][0-9]{0,4})?$/).refine((host) => {
|
|
6266
|
+
const port = host.split(":")[1];
|
|
6267
|
+
return port === void 0 || Number(port) <= 65535;
|
|
6268
|
+
});
|
|
6269
|
+
z.strictObject({
|
|
6270
|
+
provider: z.enum(FORGE_PROVIDERS),
|
|
6271
|
+
host: ForgeHostSchema.optional()
|
|
6272
|
+
});
|
|
6273
|
+
const ForgeLinkStatusSchema = z.strictObject({
|
|
6274
|
+
provider: z.enum(FORGE_PROVIDERS),
|
|
6275
|
+
host: ForgeHostSchema,
|
|
6276
|
+
state: z.enum([
|
|
6277
|
+
"pending",
|
|
6278
|
+
"linked",
|
|
6279
|
+
"relink",
|
|
6280
|
+
"failed"
|
|
6281
|
+
]),
|
|
6282
|
+
/** How the token was obtained: the platform's device flow, or given on the machine. */
|
|
6283
|
+
method: z.enum(["device", "token"]).optional(),
|
|
6284
|
+
/** The account the token belongs to (the platform's login). */
|
|
6285
|
+
account: z.string().min(1).max(100).optional(),
|
|
6286
|
+
access: z.enum([
|
|
6287
|
+
"read",
|
|
6288
|
+
"write",
|
|
6289
|
+
"unknown"
|
|
6290
|
+
]).optional(),
|
|
6291
|
+
/** The scopes or permissions the platform reported, as it names them. */
|
|
6292
|
+
scopes: z.array(z.string().min(1).max(60)).max(12).optional(),
|
|
6293
|
+
repos: z.discriminatedUnion("kind", [z.strictObject({ kind: z.literal("all") }), z.strictObject({
|
|
6294
|
+
kind: z.literal("listed"),
|
|
6295
|
+
names: z.array(z.string().min(1).max(140)).max(30),
|
|
6296
|
+
more: z.boolean()
|
|
6297
|
+
})]).optional(),
|
|
6298
|
+
linked_at: z.iso.datetime().optional(),
|
|
6299
|
+
/** When the token stops working; absent when the platform did not say. */
|
|
6300
|
+
expires_at: z.iso.datetime().optional(),
|
|
6301
|
+
pending: z.strictObject({
|
|
6302
|
+
user_code: z.string().min(1).max(32),
|
|
6303
|
+
/** The public address to enter the code at. */
|
|
6304
|
+
verification_uri: z.string().max(300),
|
|
6305
|
+
expires_at: z.iso.datetime()
|
|
6306
|
+
}).optional(),
|
|
6307
|
+
failure: z.enum([
|
|
6308
|
+
"expired",
|
|
6309
|
+
"denied",
|
|
6310
|
+
"unreachable",
|
|
6311
|
+
"rejected",
|
|
6312
|
+
"not_saved"
|
|
6313
|
+
]).optional()
|
|
6314
|
+
});
|
|
6315
|
+
const CODE_PATCH_MAX_CHARS = 4e4;
|
|
6316
|
+
const CODE_TEXT_MAX_CHARS = 2e3;
|
|
6317
|
+
/** The number of a pull request (GitHub) or merge request (GitLab) in its repository. */
|
|
6318
|
+
const CodePullNumberSchema = z.int().min(1).max(1e9);
|
|
6319
|
+
const CODE_PULL_STATES = [
|
|
6320
|
+
"open",
|
|
6321
|
+
"draft",
|
|
6322
|
+
"merged",
|
|
6323
|
+
"closed"
|
|
6324
|
+
];
|
|
6325
|
+
/** Where the checks of a pull request stand; `unknown` when the bridge did not read them. */
|
|
6326
|
+
const CODE_CHECKS_STATES = [
|
|
6327
|
+
"passed",
|
|
6328
|
+
"failed",
|
|
6329
|
+
"running",
|
|
6330
|
+
"none",
|
|
6331
|
+
"unknown"
|
|
6332
|
+
];
|
|
6333
|
+
/** Where the review of a pull request stands; `unknown` when the bridge did not read it. */
|
|
6334
|
+
const CODE_REVIEW_STATES = [
|
|
6335
|
+
"approved",
|
|
6336
|
+
"changes_requested",
|
|
6337
|
+
"pending",
|
|
6338
|
+
"none",
|
|
6339
|
+
"unknown"
|
|
6340
|
+
];
|
|
6341
|
+
/** The repository the pull requests are read from, as the machine knows it. */
|
|
6342
|
+
const CodeRepositorySchema = z.strictObject({
|
|
6343
|
+
provider: z.enum(FORGE_PROVIDERS),
|
|
6344
|
+
host: ForgeHostSchema,
|
|
6345
|
+
/** `owner/name` (GitLab: the group path, possibly nested). */
|
|
6346
|
+
path: z.string().min(3).max(200)
|
|
6347
|
+
});
|
|
6348
|
+
const CodeChecksSummarySchema = z.strictObject({
|
|
6349
|
+
state: z.enum(CODE_CHECKS_STATES),
|
|
6350
|
+
passed: z.int().min(0).max(1e4),
|
|
6351
|
+
failed: z.int().min(0).max(1e4),
|
|
6352
|
+
total: z.int().min(0).max(1e4)
|
|
6353
|
+
});
|
|
6354
|
+
const CodePullSchema = z.strictObject({
|
|
6355
|
+
number: CodePullNumberSchema,
|
|
6356
|
+
title: z.string().max(200),
|
|
6357
|
+
state: z.enum(CODE_PULL_STATES),
|
|
6358
|
+
/** The platform's login of the author. */
|
|
6359
|
+
author: z.string().min(1).max(100).optional(),
|
|
6360
|
+
/** The branch the work is on, and the branch it aims at. */
|
|
6361
|
+
branch: z.string().max(200),
|
|
6362
|
+
base: z.string().max(200),
|
|
6363
|
+
/** The address of the pull request at the platform, built by the bridge. */
|
|
6364
|
+
url: z.string().max(400),
|
|
6365
|
+
created_at: z.iso.datetime(),
|
|
6366
|
+
updated_at: z.iso.datetime(),
|
|
6367
|
+
checks: CodeChecksSummarySchema,
|
|
6368
|
+
review: z.enum(CODE_REVIEW_STATES),
|
|
6369
|
+
additions: z.int().min(0).optional(),
|
|
6370
|
+
deletions: z.int().min(0).optional(),
|
|
6371
|
+
files: z.int().min(0).optional()
|
|
6372
|
+
});
|
|
6373
|
+
const CodeCheckSchema = z.strictObject({
|
|
6374
|
+
name: z.string().min(1).max(120),
|
|
6375
|
+
state: z.enum([
|
|
6376
|
+
"passed",
|
|
6377
|
+
"failed",
|
|
6378
|
+
"running",
|
|
6379
|
+
"queued",
|
|
6380
|
+
"skipped"
|
|
6381
|
+
]),
|
|
6382
|
+
/** How long the step took, in seconds, when the platform says. */
|
|
6383
|
+
seconds: z.int().min(0).max(1e6).optional()
|
|
6384
|
+
});
|
|
6385
|
+
const CodeReviewSchema = z.strictObject({
|
|
6386
|
+
author: z.string().min(1).max(100).optional(),
|
|
6387
|
+
verdict: z.enum([
|
|
6388
|
+
"approved",
|
|
6389
|
+
"changes_requested",
|
|
6390
|
+
"commented",
|
|
6391
|
+
"dismissed"
|
|
6392
|
+
]),
|
|
6393
|
+
body: z.string().max(CODE_TEXT_MAX_CHARS).optional(),
|
|
6394
|
+
at: z.iso.datetime().optional()
|
|
6395
|
+
});
|
|
6396
|
+
/** A remark anchored to a line of a file of the pull request. */
|
|
6397
|
+
const CodeRemarkSchema = z.strictObject({
|
|
6398
|
+
id: z.string().min(1).max(40),
|
|
6399
|
+
author: z.string().min(1).max(100).optional(),
|
|
6400
|
+
path: z.string().min(1).max(1024),
|
|
6401
|
+
/** The line of the file: after the change (`new`) or before it (`old`, a removed line). */
|
|
6402
|
+
line: z.int().min(1).max(1e7),
|
|
6403
|
+
side: z.enum(["new", "old"]),
|
|
6404
|
+
body: z.string().max(CODE_TEXT_MAX_CHARS),
|
|
6405
|
+
at: z.iso.datetime().optional()
|
|
6406
|
+
});
|
|
6407
|
+
/**
|
|
6408
|
+
* One file of a difference. `patch` is the platform's unified difference without its header (it
|
|
6409
|
+
* starts at the first `@@` line), each line that looked like a secret replaced by the fixed
|
|
6410
|
+
* notice; absent for a binary or a very large file.
|
|
6411
|
+
*/
|
|
6412
|
+
const CodeDiffFileSchema = z.strictObject({
|
|
6413
|
+
path: z.string().min(1).max(1024),
|
|
6414
|
+
from: z.string().min(1).max(1024).optional(),
|
|
6415
|
+
status: z.enum([
|
|
6416
|
+
"added",
|
|
6417
|
+
"modified",
|
|
6418
|
+
"deleted",
|
|
6419
|
+
"renamed"
|
|
6420
|
+
]),
|
|
6421
|
+
additions: z.int().min(0),
|
|
6422
|
+
deletions: z.int().min(0),
|
|
6423
|
+
patch: z.string().max(CODE_PATCH_MAX_CHARS).optional(),
|
|
6424
|
+
/** The patch is cut at the longest the bridge sends. */
|
|
6425
|
+
truncated: z.literal(true).optional(),
|
|
6426
|
+
/** The platform shows no text for this file (binary, or too large for it). */
|
|
6427
|
+
unavailable: z.literal(true).optional(),
|
|
6428
|
+
/** The file is a secret's by its name: its difference is never sent. */
|
|
6429
|
+
protected: z.literal(true).optional()
|
|
6430
|
+
});
|
|
6431
|
+
/** The editors a link can open. The scheme of each is fixed on the device; nothing is run. */
|
|
6432
|
+
const CODE_EDITORS = [
|
|
6433
|
+
"vscode",
|
|
6434
|
+
"cursor",
|
|
6435
|
+
"windsurf",
|
|
6436
|
+
"vscodium",
|
|
6437
|
+
"zed",
|
|
6438
|
+
"idea",
|
|
6439
|
+
"webstorm",
|
|
6440
|
+
"pycharm"
|
|
6441
|
+
];
|
|
6442
|
+
const CODE_PLATFORMS = [
|
|
6443
|
+
"windows",
|
|
6444
|
+
"macos",
|
|
6445
|
+
"linux",
|
|
6446
|
+
"other"
|
|
6447
|
+
];
|
|
6448
|
+
/** The working copy an editor is opened on: the product's own folder, or a story's branch. */
|
|
6449
|
+
const CodeEditorRefSchema = z.discriminatedUnion("kind", [z.strictObject({ kind: z.literal("worktree") }), z.strictObject({
|
|
6450
|
+
kind: z.literal("workspace"),
|
|
6451
|
+
branch: z.string().min(1).max(200)
|
|
6452
|
+
})]);
|
|
6453
|
+
/** The request shapes (without the identifier, the version and the product, which the file reading adds). */
|
|
6454
|
+
const CodePullsRequestFields = { op: z.literal("pulls") };
|
|
6455
|
+
const CodePullRequestFields = {
|
|
6456
|
+
op: z.literal("pull"),
|
|
6457
|
+
number: CodePullNumberSchema
|
|
6458
|
+
};
|
|
6459
|
+
const CodeDiffRequestFields = {
|
|
6460
|
+
op: z.literal("pull_diff"),
|
|
6461
|
+
number: CodePullNumberSchema
|
|
6462
|
+
};
|
|
6463
|
+
const CodeEditorRequestFields = {
|
|
6464
|
+
op: z.literal("editor"),
|
|
6465
|
+
ref: CodeEditorRefSchema
|
|
6466
|
+
};
|
|
6467
|
+
/** What an answer carries. */
|
|
6468
|
+
const CodePullsDataSchema = z.strictObject({
|
|
6469
|
+
op: z.literal("pulls"),
|
|
6470
|
+
repository: CodeRepositorySchema,
|
|
6471
|
+
pulls: z.array(CodePullSchema).max(30),
|
|
6472
|
+
/** True when the platform has more than the bridge lists. */
|
|
6473
|
+
truncated: z.boolean()
|
|
6474
|
+
});
|
|
6475
|
+
const CodePullDataSchema = z.strictObject({
|
|
6476
|
+
op: z.literal("pull"),
|
|
6477
|
+
repository: CodeRepositorySchema,
|
|
6478
|
+
pull: CodePullSchema,
|
|
6479
|
+
checks: z.array(CodeCheckSchema).max(60),
|
|
6480
|
+
reviews: z.array(CodeReviewSchema).max(30),
|
|
6481
|
+
remarks: z.array(CodeRemarkSchema).max(100)
|
|
6482
|
+
});
|
|
6483
|
+
const CodeDiffDataSchema = z.strictObject({
|
|
6484
|
+
op: z.literal("pull_diff"),
|
|
6485
|
+
repository: CodeRepositorySchema,
|
|
6486
|
+
number: CodePullNumberSchema,
|
|
6487
|
+
files: z.array(CodeDiffFileSchema).max(100),
|
|
6488
|
+
/** True when the pull request changes more files than the bridge sends. */
|
|
6489
|
+
truncated: z.boolean()
|
|
6490
|
+
});
|
|
6491
|
+
const CodeEditorDataSchema = z.strictObject({
|
|
6492
|
+
op: z.literal("editor"),
|
|
6493
|
+
/** The folder of the working copy on the machine. */
|
|
6494
|
+
folder: z.string().min(1).max(1024),
|
|
6495
|
+
/** The branch checked out there, when it is on one. */
|
|
6496
|
+
branch: z.string().min(1).max(200).optional(),
|
|
6497
|
+
/** The system of the machine, so that a device of another system knows the link is not for it. */
|
|
6498
|
+
platform: z.enum(CODE_PLATFORMS),
|
|
6499
|
+
/** The editors found on the machine, best known first. */
|
|
6500
|
+
detected: z.array(z.enum(CODE_EDITORS)).max(CODE_EDITORS.length)
|
|
6501
|
+
});
|
|
6502
|
+
/** The stable codes of a refusal that only the code reading gives. */
|
|
6503
|
+
const CODE_ERROR_CODES = [
|
|
6504
|
+
"NOT_LINKED",
|
|
6505
|
+
"NO_REMOTE",
|
|
6506
|
+
"FORGE_REFUSED",
|
|
6507
|
+
"FORGE_UNREACHABLE",
|
|
6508
|
+
"FORGE_BUSY"
|
|
6509
|
+
];
|
|
6510
|
+
/** The request shape (without the identifier, the version and the product, which the file reading adds). */
|
|
6511
|
+
const CostsRequestFields = {
|
|
6512
|
+
op: z.literal("costs"),
|
|
6513
|
+
period: z.enum([
|
|
6514
|
+
"today",
|
|
6515
|
+
"week",
|
|
6516
|
+
"sprint",
|
|
6517
|
+
"month",
|
|
6518
|
+
"all"
|
|
6519
|
+
]).optional()
|
|
6520
|
+
};
|
|
6521
|
+
const Count$1 = z.int().min(0).max(Number.MAX_SAFE_INTEGER);
|
|
6522
|
+
/** What a set of measured parts consumed, and what it costs at the table's prices. */
|
|
6523
|
+
const CostFiguresSchema = z.strictObject({
|
|
6524
|
+
/** The dollars of the models the table prices; absent when `nature` is `unknown`. */
|
|
6525
|
+
usd: z.number().min(0).max(1e8).optional(),
|
|
6526
|
+
nature: z.string().min(1).max(16),
|
|
6527
|
+
tokens_in: Count$1,
|
|
6528
|
+
tokens_out: Count$1,
|
|
6529
|
+
cache_read: Count$1,
|
|
6530
|
+
cache_write: Count$1,
|
|
6531
|
+
/** Working time, in milliseconds. */
|
|
6532
|
+
active_ms: Count$1,
|
|
6533
|
+
/** The models that have no price in the table (their tokens count, their dollars do not). */
|
|
6534
|
+
unpriced: z.array(z.string().min(1).max(100)).max(8)
|
|
6535
|
+
});
|
|
6536
|
+
const CostWorkSchema = z.strictObject({
|
|
6537
|
+
kind: z.string().min(1).max(24),
|
|
6538
|
+
/** The file's name, the checkpoint, the story, ... that the work is about. */
|
|
6539
|
+
key: z.string().min(1).max(128),
|
|
6540
|
+
label: z.string().max(200).optional(),
|
|
6541
|
+
/** The agent that did it; `noah` for the creator's conversation with Noah. */
|
|
6542
|
+
agent: z.string().min(1).max(64),
|
|
6543
|
+
story: z.string().regex(/^[1-9]\d{0,5}-[1-9]\d{0,5}$/).optional(),
|
|
6544
|
+
figures: CostFiguresSchema
|
|
6545
|
+
});
|
|
6546
|
+
const CostPhaseSchema = z.strictObject({
|
|
6547
|
+
/** The phase of the method; absent for what ran when no phase was current. */
|
|
6548
|
+
phase: z.string().min(1).max(32).optional(),
|
|
6549
|
+
figures: CostFiguresSchema,
|
|
6550
|
+
works: z.array(CostWorkSchema).max(60),
|
|
6551
|
+
/** True when the phase holds more works than listed (the figures count them all). */
|
|
6552
|
+
truncated: z.boolean()
|
|
6553
|
+
});
|
|
6554
|
+
const CostAgentSchema = z.strictObject({
|
|
6555
|
+
agent: z.string().min(1).max(64),
|
|
6556
|
+
figures: CostFiguresSchema
|
|
6557
|
+
});
|
|
6558
|
+
const CostStorySchema = z.strictObject({
|
|
6559
|
+
story: z.string().regex(/^[1-9]\d{0,5}-[1-9]\d{0,5}$/),
|
|
6560
|
+
/** The sprint that carries it, when one does. */
|
|
6561
|
+
sprint: z.string().min(1).max(64).optional(),
|
|
6562
|
+
figures: CostFiguresSchema
|
|
6563
|
+
});
|
|
6564
|
+
const CostSprintSchema = z.strictObject({
|
|
6565
|
+
id: z.string().min(1).max(64),
|
|
6566
|
+
goal: z.string().max(200),
|
|
6567
|
+
/** Where the sprint stands (`in_force`, `superseded`, ...); a device shows an unknown one in neutral words. */
|
|
6568
|
+
status: z.string().min(1).max(24),
|
|
6569
|
+
stories: Count$1,
|
|
6570
|
+
/** The budget the sprint was planned with, in dollars. */
|
|
6571
|
+
budget_usd: z.number().min(0).max(1e8),
|
|
6572
|
+
figures: CostFiguresSchema
|
|
6573
|
+
});
|
|
6574
|
+
/** What the agents declared on their own, apart from the measure. */
|
|
6575
|
+
const CostDeclaredAgentSchema = z.strictObject({
|
|
6576
|
+
agent: z.string().min(1).max(64),
|
|
6577
|
+
/** Dollars declared as real, and as estimated. */
|
|
6578
|
+
usd: z.number().min(0).max(1e8),
|
|
6579
|
+
usd_estimated: z.number().min(0).max(1e8),
|
|
6580
|
+
tokens_in: Count$1,
|
|
6581
|
+
tokens_out: Count$1
|
|
6582
|
+
});
|
|
6583
|
+
const CostDeclaredSchema = z.strictObject({
|
|
6584
|
+
usd: z.number().min(0).max(1e8),
|
|
6585
|
+
usd_estimated: z.number().min(0).max(1e8),
|
|
6586
|
+
tokens_in: Count$1,
|
|
6587
|
+
tokens_out: Count$1,
|
|
6588
|
+
agents: z.array(CostDeclaredAgentSchema).max(60)
|
|
6589
|
+
});
|
|
6590
|
+
const CostsDataSchema = z.strictObject({
|
|
6591
|
+
op: z.literal("costs"),
|
|
6592
|
+
period: z.string().min(1).max(16),
|
|
6593
|
+
/** The price table the dollars were computed with. */
|
|
6594
|
+
table: z.strictObject({
|
|
6595
|
+
version: z.string().min(1).max(32),
|
|
6596
|
+
as_of: z.string().min(1).max(32)
|
|
6597
|
+
}),
|
|
6598
|
+
/** False when no session was measured in the period: no figure, never a zero. */
|
|
6599
|
+
measured: z.boolean(),
|
|
6600
|
+
/** How many measured sessions the figures come from. */
|
|
6601
|
+
sessions: Count$1,
|
|
6602
|
+
total: CostFiguresSchema,
|
|
6603
|
+
phases: z.array(CostPhaseSchema).max(12),
|
|
6604
|
+
agents: z.array(CostAgentSchema).max(60),
|
|
6605
|
+
stories: z.array(CostStorySchema).max(400),
|
|
6606
|
+
sprints: z.array(CostSprintSchema).max(60),
|
|
6607
|
+
/** Absent when no agent declared a cost (or the period is not `all`: declarations carry no date). */
|
|
6608
|
+
declared: CostDeclaredSchema.optional(),
|
|
6609
|
+
/** True when a list was cut at its limit (the totals count everything). */
|
|
6610
|
+
truncated: z.boolean()
|
|
6611
|
+
});
|
|
6612
|
+
//#endregion
|
|
5360
6613
|
//#region ../../packages/protocol/dist/install.js
|
|
5361
6614
|
/**
|
|
5362
6615
|
* The installations the approved architecture plans. The architecture document
|
|
@@ -6010,216 +7263,303 @@ const AgentDelegatedPayloadSchema = z.strictObject({
|
|
|
6010
7263
|
brief: BriefSchema
|
|
6011
7264
|
});
|
|
6012
7265
|
//#endregion
|
|
6013
|
-
//#region ../../packages/protocol/dist/
|
|
7266
|
+
//#region ../../packages/protocol/dist/trace.js
|
|
6014
7267
|
/**
|
|
6015
|
-
*
|
|
6016
|
-
*
|
|
6017
|
-
*
|
|
6018
|
-
*
|
|
6019
|
-
*
|
|
6020
|
-
*
|
|
6021
|
-
*
|
|
6022
|
-
* Generic on purpose: schemas and rules only, no transport, no account, no offer.
|
|
7268
|
+
* Traceability and deviations. Each requirement of the PRD (`EF-n`, `ENF-n`) is linked,
|
|
7269
|
+
* at read time, to the stories that cite it, their pull requests, the tests that cite it and the
|
|
7270
|
+
* deviations recorded on it: nothing of that link is ever written in a document. A deviation is a
|
|
7271
|
+
* signed event of the journal: an agent (the QA reviewer) found the delivered work drifting from
|
|
7272
|
+
* requirements a started story cites. Its id, like an item of deferred work's, is `V-` then the
|
|
7273
|
+
* last 10 characters of the ULID of its event, never written in the `payload`, never reused.
|
|
6023
7274
|
*/
|
|
6024
|
-
/** Event types of the
|
|
6025
|
-
const
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
rootCertified: "device.root_certified",
|
|
6029
|
-
confirmed: "device.confirmed",
|
|
6030
|
-
dismissed: "trust.dismissed"
|
|
7275
|
+
/** Event types of the deviations, each at version 1. */
|
|
7276
|
+
const DEVIATION_EVENT_TYPES = {
|
|
7277
|
+
reported: "deviation.reported",
|
|
7278
|
+
resolved: "deviation.resolved"
|
|
6031
7279
|
};
|
|
6032
|
-
/**
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
DEVICE_TRUST_EVENT_TYPES.revoked,
|
|
6040
|
-
DEVICE_TRUST_EVENT_TYPES.rootCertified,
|
|
6041
|
-
DEVICE_TRUST_EVENT_TYPES.confirmed
|
|
6042
|
-
]);
|
|
6043
|
-
new Set(Object.values(DEVICE_TRUST_EVENT_TYPES));
|
|
6044
|
-
/** The kinds of device a `device.paired` registers (the creator's key has its own events). */
|
|
6045
|
-
const PAIRED_DEVICE_KINDS = [
|
|
6046
|
-
"machine",
|
|
6047
|
-
"phone",
|
|
6048
|
-
"browser"
|
|
7280
|
+
/** Where a requirement stands: to do, in development, in review, delivered, or deviating. */
|
|
7281
|
+
const REQUIREMENT_STATUSES = [
|
|
7282
|
+
"todo",
|
|
7283
|
+
"in_development",
|
|
7284
|
+
"in_review",
|
|
7285
|
+
"delivered",
|
|
7286
|
+
"deviation"
|
|
6049
7287
|
];
|
|
6050
|
-
/**
|
|
6051
|
-
const
|
|
6052
|
-
/**
|
|
6053
|
-
const
|
|
6054
|
-
/**
|
|
6055
|
-
const
|
|
6056
|
-
/**
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
}
|
|
6061
|
-
/** The algorithms of the linked devices (the `webauthn-*` ones serve the terminal's key only). */
|
|
6062
|
-
const DEVICE_SIG_ALG = "ed25519";
|
|
6063
|
-
const DEVICE_ENC_ALG = "x25519";
|
|
6064
|
-
/** 32 bytes in base64url: 43 characters, the last one carrying 4 bits only. */
|
|
6065
|
-
const PUBLIC_KEY_32 = /^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/;
|
|
6066
|
-
/** 16 random bytes in base64url: 22 characters, the last one carrying 2 bits only. */
|
|
6067
|
-
const RANDOM_16 = /^[A-Za-z0-9_-]{21}[AQgw]$/;
|
|
6068
|
-
/** `sha256:` then the 32-byte digest in base64url (an event's hash). */
|
|
6069
|
-
const TRUST_EVENT_HASH_PATTERN = /^sha256:[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/;
|
|
6070
|
-
const publicKey32 = z.string().regex(PUBLIC_KEY_32).refine(isCanonicalBase64url);
|
|
6071
|
-
const utcDatetime = z.iso.datetime();
|
|
6072
|
-
/** A public key of a device with its algorithm: `{ alg, key }`. */
|
|
6073
|
-
const DeviceSigKeySchema = z.strictObject({
|
|
6074
|
-
alg: z.literal(DEVICE_SIG_ALG),
|
|
6075
|
-
key: publicKey32
|
|
6076
|
-
});
|
|
6077
|
-
const DeviceEncKeySchema = z.strictObject({
|
|
6078
|
-
alg: z.literal(DEVICE_ENC_ALG),
|
|
6079
|
-
key: publicKey32
|
|
6080
|
-
});
|
|
6081
|
-
/**
|
|
6082
|
-
* Every signed structure of trust carries its label `t` (type and version), and a key signs the
|
|
6083
|
-
* types it knows only: an authorization is never taken for an order, nor a relay's challenge for
|
|
6084
|
-
* an authorization. The domain separation stops a signature made for one purpose from
|
|
6085
|
-
* serving another.
|
|
6086
|
-
*/
|
|
6087
|
-
const TRUST_SIGNATURE_TAGS = {
|
|
6088
|
-
devicePaired: "device.paired.auth/v1",
|
|
6089
|
-
deviceRevoked: "device.revoked.auth/v1",
|
|
6090
|
-
pairOffer: "pair.offer/v1",
|
|
6091
|
-
rootCertified: "device.root_certified.auth/v1",
|
|
6092
|
-
message: "msg.inner/v1",
|
|
6093
|
-
linkWelcome: "link.welcome/v1"
|
|
6094
|
-
};
|
|
6095
|
-
/** True for a label this engine knows. */
|
|
6096
|
-
function isTrustSignatureTag(tag) {
|
|
6097
|
-
return Object.values(TRUST_SIGNATURE_TAGS).includes(tag);
|
|
7288
|
+
/** The requirements traced: the functional and non-functional ones of the PRD. */
|
|
7289
|
+
const TRACED_REQUIREMENT_PATTERN = /^(?:EF|ENF)-[1-9]\d*$/;
|
|
7290
|
+
/** A traced requirement as typed (`ef-18`, with spaces): trimmed, in upper case, then checked. */
|
|
7291
|
+
const TracedRequirementInputSchema = z.string().trim().toUpperCase().pipe(z.string().regex(TRACED_REQUIREMENT_PATTERN, { message: "expected EF-n or ENF-n, for example EF-18" }));
|
|
7292
|
+
/** `V-` then the last 10 characters of the ULID of the event that reported the deviation. */
|
|
7293
|
+
const DeviationIdSchema = z.string().regex(/^V-[0-9A-HJKMNP-TV-Z]{10}$/, { message: "expected V- then 10 characters, for example V-7M2K9QX4TB" });
|
|
7294
|
+
/** An id as typed (`v-7m2k9qx4tb`, with spaces): trimmed and in upper case, then checked. */
|
|
7295
|
+
const DeviationIdInputSchema = z.string().trim().toUpperCase().pipe(DeviationIdSchema);
|
|
7296
|
+
/** The id of the deviation reported by the event `eventId`: stable, never renumbered. */
|
|
7297
|
+
function deviationIdOf(eventId) {
|
|
7298
|
+
return `V-${eventId.slice(-10)}`;
|
|
6098
7299
|
}
|
|
7300
|
+
const NO_CONTROL$2 = { message: "expected no control character" };
|
|
7301
|
+
const SummarySchema$1 = z.string().trim().min(1).max(200).refine((summary) => !/[\r\n]/.test(summary), { message: "expected a single line" }).refine((summary) => !hasControlCharacter(summary, false), NO_CONTROL$2);
|
|
7302
|
+
const DetailSchema = z.string().trim().min(1).max(2e3).refine((detail) => !hasControlCharacter(detail, true), NO_CONTROL$2);
|
|
7303
|
+
const ReasonSchema = z.string().trim().min(1).max(1e3).refine((reason) => !hasControlCharacter(reason, true), NO_CONTROL$2);
|
|
7304
|
+
const RequirementIdSchema = z.string().regex(REQUIREMENT_ID_PATTERN, { message: "expected a requirement id (EF-n, ENF-n, PU-n, MS-n or AD-n)" });
|
|
7305
|
+
const requirementsOf = (id) => z.array(id).min(1).max(20).refine((ids) => new Set(ids).size === ids.length, { message: "requirements must differ" });
|
|
6099
7306
|
/**
|
|
6100
|
-
*
|
|
6101
|
-
*
|
|
6102
|
-
* its authority, once.
|
|
6103
|
-
*/
|
|
6104
|
-
const PairOfferSchema = z.strictObject({
|
|
6105
|
-
x: z.string().regex(RANDOM_16).refine(isCanonicalBase64url),
|
|
6106
|
-
expires_at: utcDatetime
|
|
6107
|
-
});
|
|
6108
|
-
/**
|
|
6109
|
-
* The proof that the creator wanted this: the signature, by a device already trusted (or by the
|
|
6110
|
-
* root, from the command run on the root machine), of the record of the event (`order` absent) or
|
|
6111
|
-
* of the signed order `pair.offer` (`order` present).
|
|
7307
|
+
* `payload` of `deviation.reported`. The story is the envelope's (required); `pr` is the number of
|
|
7308
|
+
* the story's pull request when the deviation was reported, set by the engine from the journal.
|
|
6112
7309
|
*/
|
|
6113
|
-
const
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
const DevicePairedPayloadSchema = z.strictObject({
|
|
6121
|
-
device: z.string().regex(PAIRED_DEVICE_ID_PATTERN),
|
|
6122
|
-
kind: z.enum(PAIRED_DEVICE_KINDS),
|
|
6123
|
-
sig: DeviceSigKeySchema,
|
|
6124
|
-
enc: DeviceEncKeySchema,
|
|
6125
|
-
/** The bridge of the perimeter: the authorization holds in the journals this bridge writes. */
|
|
6126
|
-
bridge: z.string().regex(BRIDGE_ID_PATTERN),
|
|
6127
|
-
/** True until the handover is complete (`device.confirmed`): the bridge alone may revoke it. */
|
|
6128
|
-
provisional: z.boolean(),
|
|
6129
|
-
issued_at: utcDatetime,
|
|
6130
|
-
authorization: AuthorizationSchema
|
|
6131
|
-
}).refine((payload) => payload.device.startsWith(`${payload.kind}:`), {
|
|
6132
|
-
path: ["kind"],
|
|
6133
|
-
message: "the kind is the prefix of the device"
|
|
6134
|
-
});
|
|
6135
|
-
/** `payload` of `device.revoked` v1. */
|
|
6136
|
-
const DeviceRevokedPayloadSchema = z.strictObject({
|
|
6137
|
-
device: z.string().regex(PAIRED_DEVICE_ID_PATTERN),
|
|
6138
|
-
/**
|
|
6139
|
-
* For a machine that writes to the journal: the hash of its last event the author knew. The
|
|
6140
|
-
* projection accepts of a revoked machine only the ancestors of this head (and of the heads of
|
|
6141
|
-
* the other revocations of it). Null for a device that writes nothing.
|
|
6142
|
-
*/
|
|
6143
|
-
chain_head: z.string().regex(TRUST_EVENT_HASH_PATTERN).nullable(),
|
|
6144
|
-
issued_at: utcDatetime,
|
|
6145
|
-
author: z.string().regex(DEVICE_ID_PATTERN),
|
|
6146
|
-
authorization: z.strictObject({
|
|
6147
|
-
alg: z.literal(DEVICE_SIG_ALG),
|
|
6148
|
-
sig: Ed25519SignatureSchema
|
|
6149
|
-
})
|
|
6150
|
-
}).refine((payload) => payload.chain_head === null || payload.device.startsWith("machine:"), {
|
|
6151
|
-
path: ["chain_head"],
|
|
6152
|
-
message: "only a machine has a chain head"
|
|
6153
|
-
});
|
|
6154
|
-
/** `payload` of `device.root_certified` v1: the first phone recognises the root. */
|
|
6155
|
-
const DeviceRootCertifiedPayloadSchema = z.strictObject({
|
|
6156
|
-
root: z.strictObject({
|
|
6157
|
-
device: z.string().regex(DEVICE_ID_PATTERN),
|
|
6158
|
-
key: publicKey32
|
|
6159
|
-
}),
|
|
6160
|
-
issued_at: utcDatetime,
|
|
6161
|
-
authorization: AuthorizationSchema
|
|
6162
|
-
});
|
|
6163
|
-
/** `payload` of `device.confirmed` v1: the handover of the device is complete. */
|
|
6164
|
-
const DeviceConfirmedPayloadSchema = z.strictObject({
|
|
6165
|
-
device: z.string().regex(PAIRED_DEVICE_ID_PATTERN),
|
|
6166
|
-
issued_at: utcDatetime
|
|
6167
|
-
});
|
|
6168
|
-
/** `payload` of `trust.dismissed` v1: the events (their hashes) the creator dismisses. */
|
|
6169
|
-
const TrustDismissedPayloadSchema = z.strictObject({
|
|
6170
|
-
events: z.array(z.string().regex(TRUST_EVENT_HASH_PATTERN)).min(1).max(16),
|
|
6171
|
-
issued_at: utcDatetime
|
|
7310
|
+
const DeviationReportedPayloadSchema = z.strictObject({
|
|
7311
|
+
requirements: requirementsOf(RequirementIdSchema),
|
|
7312
|
+
summary: SummarySchema$1,
|
|
7313
|
+
detail: DetailSchema.optional(),
|
|
7314
|
+
pr: z.number().int().min(1).max(Number.MAX_SAFE_INTEGER).optional(),
|
|
7315
|
+
/** The commit compared with the requirements. */
|
|
7316
|
+
commit: CommitIdSchema.optional()
|
|
6172
7317
|
});
|
|
6173
|
-
/**
|
|
6174
|
-
const
|
|
6175
|
-
|
|
6176
|
-
|
|
7318
|
+
/** `payload` of `deviation.resolved`: the deviation, and how it was fixed or why it is lifted. */
|
|
7319
|
+
const DeviationResolvedPayloadSchema = z.strictObject({
|
|
7320
|
+
deviation: DeviationIdSchema,
|
|
7321
|
+
reason: ReasonSchema
|
|
7322
|
+
});
|
|
7323
|
+
/** A requirement id as typed: in upper case, then checked. */
|
|
7324
|
+
const RequirementInputSchema = z.string().trim().toUpperCase().pipe(RequirementIdSchema);
|
|
7325
|
+
/** Input of `report_deviation`: the story brought to its canonical form. */
|
|
7326
|
+
const ReportDeviationInputSchema = z.strictObject({
|
|
7327
|
+
agent: AgentIdSchema.max(64),
|
|
7328
|
+
story: StoryIdSchema.overwrite(canonicalStoryId),
|
|
7329
|
+
requirements: requirementsOf(RequirementInputSchema),
|
|
7330
|
+
summary: SummarySchema$1,
|
|
7331
|
+
detail: DetailSchema.optional(),
|
|
7332
|
+
commit: CommitIdSchema.optional()
|
|
7333
|
+
});
|
|
7334
|
+
/** Input of `lift_deviation`: the agent that reported it lifts it, with why. */
|
|
7335
|
+
const ResolveDeviationInputSchema = z.strictObject({
|
|
7336
|
+
agent: AgentIdSchema.max(64),
|
|
7337
|
+
deviation: DeviationIdInputSchema,
|
|
7338
|
+
reason: ReasonSchema
|
|
7339
|
+
});
|
|
7340
|
+
/** The reason the creator gives at the terminal (`trace resolve`). */
|
|
7341
|
+
const DeviationReasonSchema = ReasonSchema;
|
|
7342
|
+
/** Input of `get_trace`: one requirement, or every one when absent. */
|
|
7343
|
+
const GetTraceInputSchema = z.strictObject({ requirement: z.string().max(32).optional() });
|
|
7344
|
+
const DOCS_TEXT_MAX_CHARS = 2e3;
|
|
7345
|
+
/** Where a story stands, as the engine sees it. */
|
|
7346
|
+
const DOCS_STORY_STATUSES = [
|
|
7347
|
+
"todo",
|
|
7348
|
+
"in_progress",
|
|
7349
|
+
"in_review",
|
|
7350
|
+
"blocked",
|
|
7351
|
+
"done"
|
|
7352
|
+
];
|
|
7353
|
+
const StoryRef = z.string().regex(/^[1-9]\d{0,5}-[1-9]\d{0,5}$/);
|
|
7354
|
+
const RequirementRef = z.string().regex(TRACED_REQUIREMENT_PATTERN).max(16);
|
|
7355
|
+
/** The request shapes (without the identifier, the version and the product, which the file reading adds). */
|
|
7356
|
+
const DocsListRequestFields = { op: z.literal("docs") };
|
|
7357
|
+
const DocsTraceRequestFields = {
|
|
7358
|
+
op: z.literal("trace"),
|
|
7359
|
+
requirement: RequirementRef
|
|
7360
|
+
};
|
|
7361
|
+
const DocsRetrosRequestFields = { op: z.literal("retros") };
|
|
7362
|
+
/** One document of the product: a file of `docs/` or `epics/` (the stories are told by the breakdown). */
|
|
7363
|
+
const DocsDocumentSchema = z.strictObject({
|
|
7364
|
+
/** The path from the product's folder, with `/`. */
|
|
7365
|
+
path: z.string().min(1).max(300),
|
|
7366
|
+
/** The header's kind (`prd`, `architecture`...), or `unknown` when the header is not valid. */
|
|
7367
|
+
kind: z.string().min(1).max(32),
|
|
7368
|
+
id: z.string().min(1).max(64).optional(),
|
|
7369
|
+
title: z.string().min(1).max(200).optional(),
|
|
7370
|
+
/** The agent that owns it, or `creator`. */
|
|
7371
|
+
owner: z.string().min(1).max(64).optional(),
|
|
7372
|
+
standing: z.string().min(1).max(32),
|
|
7373
|
+
/** The requirements it defines (active and retired), when it defines any. */
|
|
7374
|
+
requirements: z.int().min(0).max(1e5).optional(),
|
|
7375
|
+
/** The documents and requirements its header links to. */
|
|
7376
|
+
links: z.array(z.string().min(1).max(64)).max(40).optional()
|
|
7377
|
+
});
|
|
7378
|
+
const DocsRequirementSchema = z.strictObject({
|
|
7379
|
+
id: RequirementRef,
|
|
7380
|
+
text: z.string().max(400),
|
|
7381
|
+
/** The document that defines it. */
|
|
7382
|
+
path: z.string().min(1).max(300),
|
|
7383
|
+
/** Retired: the id stays reserved, the status below is not meaningful. */
|
|
7384
|
+
retired: z.boolean(),
|
|
7385
|
+
status: z.enum(REQUIREMENT_STATUSES),
|
|
7386
|
+
stories: z.array(StoryRef).max(100),
|
|
7387
|
+
/** The test files that cite it, or null when the tests could not be read. */
|
|
7388
|
+
tests: z.int().min(0).max(1e5).nullable(),
|
|
7389
|
+
pull_requests: z.int().min(0).max(1e5),
|
|
7390
|
+
/** Its open deviations. */
|
|
7391
|
+
deviations: z.int().min(0).max(1e5)
|
|
7392
|
+
});
|
|
7393
|
+
const DocsDeviationSchema = z.strictObject({
|
|
7394
|
+
id: z.string().regex(/^V-[0-9A-HJKMNP-TV-Z]{10}$/),
|
|
7395
|
+
story: StoryRef,
|
|
7396
|
+
requirements: z.array(z.string().min(1).max(16)).max(20),
|
|
7397
|
+
summary: z.string().max(200),
|
|
7398
|
+
reported_by: z.string().min(1).max(64),
|
|
7399
|
+
reported_at: z.string().max(40)
|
|
7400
|
+
});
|
|
7401
|
+
const DocsBreakdownStorySchema = z.strictObject({
|
|
7402
|
+
id: StoryRef,
|
|
7403
|
+
title: z.string().min(1).max(200).optional(),
|
|
7404
|
+
status: z.enum(DOCS_STORY_STATUSES),
|
|
7405
|
+
depends_on: z.array(StoryRef).max(40),
|
|
7406
|
+
requirements: z.array(RequirementRef).max(40)
|
|
7407
|
+
});
|
|
7408
|
+
const DocsEpicSchema = z.strictObject({
|
|
7409
|
+
/** `epic-2`. */
|
|
7410
|
+
id: z.string().regex(/^epic-[1-9]\d{0,5}$/),
|
|
7411
|
+
title: z.string().min(1).max(200).optional(),
|
|
7412
|
+
/** The path of the epic's document, when it has one. */
|
|
7413
|
+
path: z.string().min(1).max(300).optional(),
|
|
7414
|
+
out_of_scope: z.array(RequirementRef).max(200),
|
|
7415
|
+
stories: z.array(DocsBreakdownStorySchema).max(600)
|
|
7416
|
+
});
|
|
7417
|
+
const DocsCoverageSchema = z.strictObject({
|
|
7418
|
+
/** The active functional and non-functional requirements. */
|
|
7419
|
+
total: z.int().min(0).max(1e5),
|
|
7420
|
+
/** Those a story carries. */
|
|
7421
|
+
covered: z.int().min(0).max(1e5),
|
|
7422
|
+
/** Those an epic leaves aside. */
|
|
7423
|
+
out_of_scope: z.int().min(0).max(1e5),
|
|
7424
|
+
/** The others, by id. */
|
|
7425
|
+
uncovered: z.array(RequirementRef).max(200)
|
|
7426
|
+
});
|
|
7427
|
+
const DOCS_TESTS_STATES = [
|
|
7428
|
+
"read",
|
|
7429
|
+
"truncated",
|
|
7430
|
+
"unavailable"
|
|
7431
|
+
];
|
|
7432
|
+
const CountsSchema = z.strictObject({
|
|
7433
|
+
todo: z.int().min(0),
|
|
7434
|
+
in_development: z.int().min(0),
|
|
7435
|
+
in_review: z.int().min(0),
|
|
7436
|
+
delivered: z.int().min(0),
|
|
7437
|
+
deviation: z.int().min(0)
|
|
7438
|
+
});
|
|
7439
|
+
const DocsListDataSchema = z.strictObject({
|
|
7440
|
+
op: z.literal("docs"),
|
|
7441
|
+
documents: z.array(DocsDocumentSchema).max(200),
|
|
7442
|
+
/** True when the product holds more documents than the bridge lists. */
|
|
7443
|
+
truncated: z.boolean(),
|
|
7444
|
+
requirements: z.array(DocsRequirementSchema).max(800),
|
|
7445
|
+
/** How many active requirements stand at each status. */
|
|
7446
|
+
counts: CountsSchema,
|
|
7447
|
+
/** How the tests were read: all of them, a part, or not at all. */
|
|
7448
|
+
tests: z.enum(DOCS_TESTS_STATES),
|
|
7449
|
+
/** The open deviations. */
|
|
7450
|
+
deviations: z.array(DocsDeviationSchema).max(50),
|
|
7451
|
+
epics: z.array(DocsEpicSchema).max(100),
|
|
7452
|
+
/** Absent while no story exists: nothing is covered or not yet. */
|
|
7453
|
+
coverage: DocsCoverageSchema.optional()
|
|
7454
|
+
});
|
|
7455
|
+
const DocsTraceDeviationSchema = DocsDeviationSchema.extend({
|
|
7456
|
+
detail: z.string().max(DOCS_TEXT_MAX_CHARS).optional(),
|
|
7457
|
+
status: z.enum(["open", "resolved"]),
|
|
7458
|
+
resolved: z.strictObject({
|
|
7459
|
+
reason: z.string().max(DOCS_TEXT_MAX_CHARS),
|
|
7460
|
+
by: z.string().min(1).max(64),
|
|
7461
|
+
at: z.string().max(40)
|
|
7462
|
+
}).optional()
|
|
7463
|
+
});
|
|
7464
|
+
const DocsTraceDataSchema = z.strictObject({
|
|
7465
|
+
op: z.literal("trace"),
|
|
7466
|
+
requirement: z.strictObject({
|
|
7467
|
+
id: RequirementRef,
|
|
7468
|
+
text: z.string().max(400),
|
|
7469
|
+
path: z.string().min(1).max(300),
|
|
7470
|
+
line: z.int().min(1).max(1e7),
|
|
7471
|
+
retired: z.boolean(),
|
|
7472
|
+
status: z.enum(REQUIREMENT_STATUSES),
|
|
7473
|
+
stories: z.array(z.strictObject({
|
|
7474
|
+
id: StoryRef,
|
|
7475
|
+
title: z.string().min(1).max(200).optional(),
|
|
7476
|
+
status: z.enum(DOCS_STORY_STATUSES)
|
|
7477
|
+
})).max(100),
|
|
7478
|
+
tests: z.array(z.strictObject({
|
|
7479
|
+
path: z.string().min(1).max(300),
|
|
7480
|
+
/** 0: the file's name cites it. */
|
|
7481
|
+
line: z.int().min(0).max(1e7)
|
|
7482
|
+
})).max(100),
|
|
7483
|
+
pull_requests: z.array(z.strictObject({
|
|
7484
|
+
story: StoryRef,
|
|
7485
|
+
number: z.int().min(1).max(1e9)
|
|
7486
|
+
})).max(100),
|
|
7487
|
+
deviations: z.array(DocsTraceDeviationSchema).max(100)
|
|
7488
|
+
}),
|
|
7489
|
+
tests_state: z.enum(DOCS_TESTS_STATES)
|
|
7490
|
+
});
|
|
7491
|
+
/** Where a retrospective stands: the creator's answer to its checkpoint decides. */
|
|
7492
|
+
const RETRO_STATUSES = [
|
|
7493
|
+
"open",
|
|
7494
|
+
"approved",
|
|
7495
|
+
"changes_requested",
|
|
7496
|
+
"dismissed"
|
|
7497
|
+
];
|
|
7498
|
+
/**
|
|
7499
|
+
* What became of a proposed lesson: still `proposed` (nothing recorded), `active` (recorded and in
|
|
7500
|
+
* force), `retired` or `superseded` since, or `not_kept` (its retrospective was not approved).
|
|
7501
|
+
*/
|
|
7502
|
+
const RETRO_LESSON_STATUSES = [
|
|
7503
|
+
"proposed",
|
|
7504
|
+
"active",
|
|
7505
|
+
"retired",
|
|
7506
|
+
"superseded",
|
|
7507
|
+
"not_kept"
|
|
7508
|
+
];
|
|
7509
|
+
const LessonText = z.string().max(DOCS_TEXT_MAX_CHARS);
|
|
7510
|
+
const LessonRef = z.string().regex(/^L-[0-9A-HJKMNP-TV-Z]{10}-[1-9][0-9]?$/);
|
|
7511
|
+
const DocsRetroSchema = z.strictObject({
|
|
7512
|
+
id: z.string().regex(/^R-[0-9A-HJKMNP-TV-Z]{10}$/),
|
|
7513
|
+
scope: z.strictObject({
|
|
7514
|
+
kind: z.enum(["epic", "sprint"]),
|
|
7515
|
+
id: z.string().min(1).max(16)
|
|
7516
|
+
}),
|
|
7517
|
+
at: z.string().max(40),
|
|
7518
|
+
proposed_by: z.string().min(1).max(64),
|
|
7519
|
+
status: z.enum(RETRO_STATUSES),
|
|
7520
|
+
summary: LessonText,
|
|
7521
|
+
lessons: z.array(z.strictObject({
|
|
7522
|
+
id: LessonRef,
|
|
7523
|
+
title: z.string().max(200),
|
|
7524
|
+
text: LessonText,
|
|
7525
|
+
tags: z.array(z.string().min(1).max(32)).max(5),
|
|
7526
|
+
agents: z.array(z.string().min(1).max(64)).max(10),
|
|
7527
|
+
status: z.enum(RETRO_LESSON_STATUSES)
|
|
7528
|
+
})).max(12),
|
|
7529
|
+
retire: z.array(z.strictObject({
|
|
7530
|
+
lesson: LessonRef,
|
|
7531
|
+
reason: LessonText
|
|
7532
|
+
})).max(12),
|
|
7533
|
+
actions: z.array(z.strictObject({
|
|
7534
|
+
id: z.string().min(1).max(32),
|
|
7535
|
+
text: LessonText,
|
|
7536
|
+
owner: z.string().min(1).max(64).optional()
|
|
7537
|
+
})).max(12)
|
|
7538
|
+
});
|
|
7539
|
+
/** A lesson in force: what the agents read at the start of a task. */
|
|
7540
|
+
const DocsMemoryLessonSchema = z.strictObject({
|
|
7541
|
+
id: LessonRef,
|
|
7542
|
+
title: z.string().max(200),
|
|
7543
|
+
text: LessonText,
|
|
7544
|
+
tags: z.array(z.string().min(1).max(32)).max(5),
|
|
7545
|
+
agents: z.array(z.string().min(1).max(64)).max(10),
|
|
7546
|
+
at: z.string().max(40),
|
|
7547
|
+
/** The retrospective it comes from. */
|
|
7548
|
+
retro: z.string().regex(/^R-[0-9A-HJKMNP-TV-Z]{10}$/),
|
|
7549
|
+
/** True when it was also promoted to the common memory of the machine. */
|
|
7550
|
+
promoted: z.boolean()
|
|
7551
|
+
});
|
|
7552
|
+
const DocsRetrosDataSchema = z.strictObject({
|
|
7553
|
+
op: z.literal("retros"),
|
|
7554
|
+
retrospectives: z.array(DocsRetroSchema).max(40),
|
|
7555
|
+
/** The lessons in force, newest first. */
|
|
7556
|
+
memory: z.array(DocsMemoryLessonSchema).max(120),
|
|
7557
|
+
/**
|
|
7558
|
+
* The retrospectives whose content this machine could not read (the product's key is missing
|
|
7559
|
+
* here): they are counted, never guessed.
|
|
7560
|
+
*/
|
|
7561
|
+
unreadable: z.int().min(0).max(1e5)
|
|
6177
7562
|
});
|
|
6178
|
-
/**
|
|
6179
|
-
* What the authorization of a `device.paired` signs: its payload without `authorization`, plus the
|
|
6180
|
-
* label. Independent of the product and of `prev`: the bridge writes it in each journal it serves,
|
|
6181
|
-
* but cannot make one without the signer.
|
|
6182
|
-
*/
|
|
6183
|
-
function devicePairedRecord(payload) {
|
|
6184
|
-
return {
|
|
6185
|
-
t: TRUST_SIGNATURE_TAGS.devicePaired,
|
|
6186
|
-
device: payload.device,
|
|
6187
|
-
kind: payload.kind,
|
|
6188
|
-
sig: payload.sig,
|
|
6189
|
-
enc: payload.enc,
|
|
6190
|
-
bridge: payload.bridge,
|
|
6191
|
-
provisional: payload.provisional,
|
|
6192
|
-
issued_at: payload.issued_at
|
|
6193
|
-
};
|
|
6194
|
-
}
|
|
6195
|
-
/** What the signed order `pair.offer` covers. */
|
|
6196
|
-
function pairOfferRecord(input) {
|
|
6197
|
-
return {
|
|
6198
|
-
t: TRUST_SIGNATURE_TAGS.pairOffer,
|
|
6199
|
-
kind: input.kind,
|
|
6200
|
-
bridge: input.bridge,
|
|
6201
|
-
x: input.order.x,
|
|
6202
|
-
expires_at: input.order.expires_at
|
|
6203
|
-
};
|
|
6204
|
-
}
|
|
6205
|
-
/** What the authorization of a `device.revoked` signs. */
|
|
6206
|
-
function deviceRevokedRecord(payload) {
|
|
6207
|
-
return {
|
|
6208
|
-
t: TRUST_SIGNATURE_TAGS.deviceRevoked,
|
|
6209
|
-
device: payload.device,
|
|
6210
|
-
chain_head: payload.chain_head,
|
|
6211
|
-
issued_at: payload.issued_at,
|
|
6212
|
-
author: payload.author
|
|
6213
|
-
};
|
|
6214
|
-
}
|
|
6215
|
-
/** What the authorization of a `device.root_certified` signs. */
|
|
6216
|
-
function rootCertifiedRecord(payload) {
|
|
6217
|
-
return {
|
|
6218
|
-
t: TRUST_SIGNATURE_TAGS.rootCertified,
|
|
6219
|
-
root: payload.root,
|
|
6220
|
-
issued_at: payload.issued_at
|
|
6221
|
-
};
|
|
6222
|
-
}
|
|
6223
7563
|
//#endregion
|
|
6224
7564
|
//#region ../../packages/protocol/dist/elicitation.js
|
|
6225
7565
|
/**
|
|
@@ -6322,6 +7662,25 @@ const CheckpointElicitedPayloadSchema = z.strictObject({
|
|
|
6322
7662
|
});
|
|
6323
7663
|
});
|
|
6324
7664
|
//#endregion
|
|
7665
|
+
//#region ../../packages/protocol/dist/exchange.js
|
|
7666
|
+
/**
|
|
7667
|
+
* The summary of an exchange: at the end of each exchange with the creator, Noah writes a short
|
|
7668
|
+
* note to the journal (what was asked, what was decided, what was promised). Whatever session Noah
|
|
7669
|
+
* runs in (the AI tool's or the bridge's), a fresh session reads the latest notes in the state of
|
|
7670
|
+
* the product and takes the thread up where it was left, without any session to resume.
|
|
7671
|
+
*/
|
|
7672
|
+
/** Event type of the summary, at version 1. */
|
|
7673
|
+
const EXCHANGE_EVENT_TYPES = { summarized: "exchange.summarized" };
|
|
7674
|
+
/** One line, 1 to 300 characters, no control character. */
|
|
7675
|
+
const SummarySchema = z.string().trim().min(1).max(300).refine((text) => !hasControlCharacter(text, false), { message: "expected one line without control character" }).refine((text) => !/[\u2028\u2029\u200e\u200f\u202a-\u202e\u2066-\u2069]/u.test(text), { message: "expected one line without separator or direction mark" });
|
|
7676
|
+
/** Input of `summarize`: Noah's own id and the note. */
|
|
7677
|
+
const SummarizeInputSchema = z.strictObject({
|
|
7678
|
+
agent: AgentIdSchema.max(64),
|
|
7679
|
+
summary: SummarySchema
|
|
7680
|
+
});
|
|
7681
|
+
/** `payload` of `exchange.summarized`. */
|
|
7682
|
+
const ExchangeSummarizedPayloadSchema = z.strictObject({ summary: SummarySchema });
|
|
7683
|
+
//#endregion
|
|
6325
7684
|
//#region ../../packages/protocol/dist/merge.js
|
|
6326
7685
|
/**
|
|
6327
7686
|
* The merge of a story. Once the creator approves a story's merge checkpoint
|
|
@@ -6346,14 +7705,18 @@ const DEFAULT_MERGE_METHOD = "merge";
|
|
|
6346
7705
|
* 3.28: `forge`, a merge the creator did on the forge itself (the pull request merged by hand),
|
|
6347
7706
|
* which the engine recorded from what the forge shows: no approval to wait for, the forge's own
|
|
6348
7707
|
* event is the proof (who merged it, when, on which commit). `agent`, the merge the
|
|
6349
|
-
* agent responsible decided because the creator did not keep the merges for themselves.
|
|
7708
|
+
* agent responsible decided because the creator did not keep the merges for themselves. `web`, the
|
|
7709
|
+
* merge the machine did once the signed order of a browser the creator trusts was carried out, in
|
|
7710
|
+
* the same write as that answer; `mobile`, the same for the signed order of a phone.
|
|
6350
7711
|
*/
|
|
6351
7712
|
const MERGE_SOURCES = [
|
|
6352
7713
|
"chat",
|
|
6353
7714
|
"elicitation",
|
|
6354
7715
|
"terminal",
|
|
6355
7716
|
"forge",
|
|
6356
|
-
"agent"
|
|
7717
|
+
"agent",
|
|
7718
|
+
"web",
|
|
7719
|
+
"mobile"
|
|
6357
7720
|
];
|
|
6358
7721
|
/** Most changed files one merge records (the knowledge base reads them). */
|
|
6359
7722
|
const MAX_MERGED_FILES = 1e3;
|
|
@@ -6767,6 +8130,281 @@ const LoopInputSchema = z.strictObject({
|
|
|
6767
8130
|
path: ["story"]
|
|
6768
8131
|
});
|
|
6769
8132
|
//#endregion
|
|
8133
|
+
//#region ../../packages/protocol/dist/tolerant-reader.js
|
|
8134
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
8135
|
+
const defOf = (schema) => schema._zod.def;
|
|
8136
|
+
const cloneWith = (schema, def) => schema.clone(def);
|
|
8137
|
+
function derive(schema) {
|
|
8138
|
+
const def = defOf(schema);
|
|
8139
|
+
switch (def.type) {
|
|
8140
|
+
case "object": {
|
|
8141
|
+
const shape = def.shape;
|
|
8142
|
+
const next = {};
|
|
8143
|
+
for (const key of Object.keys(shape)) next[key] = readTolerant(shape[key]);
|
|
8144
|
+
return cloneWith(schema, {
|
|
8145
|
+
...def,
|
|
8146
|
+
shape: next,
|
|
8147
|
+
catchall: void 0
|
|
8148
|
+
});
|
|
8149
|
+
}
|
|
8150
|
+
case "array": return cloneWith(schema, {
|
|
8151
|
+
...def,
|
|
8152
|
+
element: readTolerant(def.element)
|
|
8153
|
+
});
|
|
8154
|
+
case "optional":
|
|
8155
|
+
case "nullable":
|
|
8156
|
+
case "readonly":
|
|
8157
|
+
case "default":
|
|
8158
|
+
case "prefault":
|
|
8159
|
+
case "nonoptional":
|
|
8160
|
+
case "catch": return cloneWith(schema, {
|
|
8161
|
+
...def,
|
|
8162
|
+
innerType: readTolerant(def.innerType)
|
|
8163
|
+
});
|
|
8164
|
+
case "union": return cloneWith(schema, {
|
|
8165
|
+
...def,
|
|
8166
|
+
options: def.options.map((option) => readTolerant(option))
|
|
8167
|
+
});
|
|
8168
|
+
case "record": return cloneWith(schema, {
|
|
8169
|
+
...def,
|
|
8170
|
+
valueType: readTolerant(def.valueType)
|
|
8171
|
+
});
|
|
8172
|
+
case "tuple": return cloneWith(schema, {
|
|
8173
|
+
...def,
|
|
8174
|
+
items: def.items.map((item) => readTolerant(item))
|
|
8175
|
+
});
|
|
8176
|
+
case "pipe": return cloneWith(schema, {
|
|
8177
|
+
...def,
|
|
8178
|
+
in: readTolerant(def.in),
|
|
8179
|
+
out: readTolerant(def.out)
|
|
8180
|
+
});
|
|
8181
|
+
default: return schema;
|
|
8182
|
+
}
|
|
8183
|
+
}
|
|
8184
|
+
/** The same schema, reading what a device receives: unknown keys dropped, known ones as strict as before. */
|
|
8185
|
+
function readTolerant(schema) {
|
|
8186
|
+
const known = cache.get(schema);
|
|
8187
|
+
if (known !== void 0) return known;
|
|
8188
|
+
const derived = derive(schema);
|
|
8189
|
+
cache.set(schema, derived);
|
|
8190
|
+
return derived;
|
|
8191
|
+
}
|
|
8192
|
+
const ProductStageSchema = z.enum([
|
|
8193
|
+
"framing",
|
|
8194
|
+
"development",
|
|
8195
|
+
"launch",
|
|
8196
|
+
"online"
|
|
8197
|
+
]);
|
|
8198
|
+
/** How a product starts: from an idea (no repository), from a repository already on the machine, or from an empty repository. */
|
|
8199
|
+
const PRODUCT_STARTS = [
|
|
8200
|
+
"idea",
|
|
8201
|
+
"existing",
|
|
8202
|
+
"empty"
|
|
8203
|
+
];
|
|
8204
|
+
/** What a machine says of the repository of a product: none, one it made empty, or one that was already there. */
|
|
8205
|
+
const PRODUCT_REPOSITORIES = [
|
|
8206
|
+
"none",
|
|
8207
|
+
"empty",
|
|
8208
|
+
"existing"
|
|
8209
|
+
];
|
|
8210
|
+
const PRODUCT_IDEA_MAX = 4e3;
|
|
8211
|
+
/** The longest folder path a machine tells of a product (the full path on the machine). */
|
|
8212
|
+
const PRODUCT_FOLDER_PATH_MAX = 1e3;
|
|
8213
|
+
/** The identifier of a place a machine allows products in (`home`, `place-2`): the machine names it, the device only repeats it. */
|
|
8214
|
+
const PRODUCT_LOCATION_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,31}$/;
|
|
8215
|
+
const hasControl = (value) => [...value].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127);
|
|
8216
|
+
/** A name the creator gives a product: trimmed, one line, no control character. */
|
|
8217
|
+
function isProductName(value) {
|
|
8218
|
+
return value.length >= 1 && value.length <= 80 && value === value.trim() && !hasControl(value);
|
|
8219
|
+
}
|
|
8220
|
+
/**
|
|
8221
|
+
* A folder name inside a place the machine allows: one segment, never a path. It holds no
|
|
8222
|
+
* separator, no drive or stream mark, no control character, is not `.` or `..`, and does not end
|
|
8223
|
+
* with a dot or a space (which a system may drop silently).
|
|
8224
|
+
*/
|
|
8225
|
+
function isFolderName(value) {
|
|
8226
|
+
return value.length >= 1 && value.length <= 100 && value !== "." && value !== ".." && !/[\\/:*?"<>|]/.test(value) && !hasControl(value) && !/[. ]$/.test(value);
|
|
8227
|
+
}
|
|
8228
|
+
/**
|
|
8229
|
+
* A step of the way into a place: one folder name, never a path, and never a hidden one (a name
|
|
8230
|
+
* that starts with a dot). The machine resolves the steps itself and refuses what leaves the place.
|
|
8231
|
+
*/
|
|
8232
|
+
function isBrowseStep(value) {
|
|
8233
|
+
return isFolderName(value) && !value.startsWith(".");
|
|
8234
|
+
}
|
|
8235
|
+
z.strictObject({
|
|
8236
|
+
name: z.string().refine(isProductName, { message: "a name of one line, trimmed" }),
|
|
8237
|
+
start: z.enum(PRODUCT_STARTS),
|
|
8238
|
+
location: z.string().regex(PRODUCT_LOCATION_ID_PATTERN),
|
|
8239
|
+
folder: z.string().refine(isFolderName, { message: "a folder name, not a path" }).optional(),
|
|
8240
|
+
/** The folders to go through inside the place, one name each (as the machine listed them), to make the product there. */
|
|
8241
|
+
path: z.array(z.string().refine(isBrowseStep, { message: "a folder name, not a path" })).max(12).optional(),
|
|
8242
|
+
preset: z.enum(PROFILE_PRESET_IDS),
|
|
8243
|
+
stage: ProductStageSchema,
|
|
8244
|
+
idea: z.string().min(1).max(PRODUCT_IDEA_MAX).optional()
|
|
8245
|
+
}).superRefine((params, context) => {
|
|
8246
|
+
if (params.start === "existing" && params.folder === void 0) context.addIssue({
|
|
8247
|
+
code: "custom",
|
|
8248
|
+
path: ["folder"],
|
|
8249
|
+
message: "a repository to take"
|
|
8250
|
+
});
|
|
8251
|
+
if (params.start === "existing" && params.path !== void 0) context.addIssue({
|
|
8252
|
+
code: "custom",
|
|
8253
|
+
path: ["path"],
|
|
8254
|
+
message: "not for an existing one"
|
|
8255
|
+
});
|
|
8256
|
+
if (params.start !== "existing" && params.folder !== void 0) context.addIssue({
|
|
8257
|
+
code: "custom",
|
|
8258
|
+
path: ["folder"],
|
|
8259
|
+
message: "only for an existing one"
|
|
8260
|
+
});
|
|
8261
|
+
if (params.start !== "idea" && params.idea !== void 0) context.addIssue({
|
|
8262
|
+
code: "custom",
|
|
8263
|
+
path: ["idea"],
|
|
8264
|
+
message: "only with an idea"
|
|
8265
|
+
});
|
|
8266
|
+
});
|
|
8267
|
+
z.strictObject({});
|
|
8268
|
+
z.strictObject({
|
|
8269
|
+
stage: ProductStageSchema.optional(),
|
|
8270
|
+
paused: z.boolean().optional(),
|
|
8271
|
+
archived: z.boolean().optional()
|
|
8272
|
+
}).refine((params) => Object.keys(params).length > 0, { message: "at least one mark" });
|
|
8273
|
+
z.strictObject({ profile: ProfileSchema });
|
|
8274
|
+
z.strictObject({ to: SemverSchema.optional() });
|
|
8275
|
+
readTolerant(z.strictObject({
|
|
8276
|
+
version: SemverSchema,
|
|
8277
|
+
/** The version this machine would bring the product to; absent when it cannot update products. */
|
|
8278
|
+
target: SemverSchema.optional(),
|
|
8279
|
+
/** `running` while an update asked from a device is under way; `failed` when the last one did not end. */
|
|
8280
|
+
update: z.enum(["running", "failed"]).optional()
|
|
8281
|
+
}));
|
|
8282
|
+
/** What a machine says of the place it allows products in. */
|
|
8283
|
+
const ProductLocationSchema = z.strictObject({
|
|
8284
|
+
id: z.string().regex(PRODUCT_LOCATION_ID_PATTERN),
|
|
8285
|
+
/** The folder as the creator reads it (the home folder written `~`). */
|
|
8286
|
+
label: z.string().min(1).max(200),
|
|
8287
|
+
/** The folders of this place that hold a repository and no product yet: ones to start from. */
|
|
8288
|
+
folders: z.array(z.string().refine(isFolderName)).max(50),
|
|
8289
|
+
/** True for the place the creator chose the last time (the one to propose first). */
|
|
8290
|
+
last: z.literal(true).optional()
|
|
8291
|
+
});
|
|
8292
|
+
/**
|
|
8293
|
+
* A creation the machine is making, has made or could not make. It is told by its ticket (the
|
|
8294
|
+
* reference the order answered with); a finished one names the product it made. A finished or
|
|
8295
|
+
* failed one stays a few minutes in the report, so that a device that was away still learns it.
|
|
8296
|
+
*/
|
|
8297
|
+
const ProductCreationSchema = z.strictObject({
|
|
8298
|
+
id: UlidSchema,
|
|
8299
|
+
name: z.string().min(1).max(80),
|
|
8300
|
+
state: z.enum([
|
|
8301
|
+
"running",
|
|
8302
|
+
"done",
|
|
8303
|
+
"failed"
|
|
8304
|
+
]),
|
|
8305
|
+
/** The product made, once `done`. */
|
|
8306
|
+
product: UlidSchema.optional(),
|
|
8307
|
+
/** Why it failed. */
|
|
8308
|
+
code: z.enum([
|
|
8309
|
+
"folder",
|
|
8310
|
+
"repository",
|
|
8311
|
+
"engine",
|
|
8312
|
+
"link"
|
|
8313
|
+
]).optional()
|
|
8314
|
+
});
|
|
8315
|
+
/** The marks and the profile a machine reports for a product it serves. */
|
|
8316
|
+
const ProductLifeSchema = z.strictObject({
|
|
8317
|
+
stage: ProductStageSchema.optional(),
|
|
8318
|
+
paused: z.literal(true).optional(),
|
|
8319
|
+
archived: z.literal(true).optional(),
|
|
8320
|
+
repository: z.enum(PRODUCT_REPOSITORIES).optional(),
|
|
8321
|
+
/** The full path of the product's folder on this machine: told to the creator's own devices only, never published. */
|
|
8322
|
+
folder: z.string().min(1).max(PRODUCT_FOLDER_PATH_MAX).optional(),
|
|
8323
|
+
profile: ProfileSchema.optional(),
|
|
8324
|
+
autonomy: ProductAutonomySchema.optional()
|
|
8325
|
+
});
|
|
8326
|
+
//#endregion
|
|
8327
|
+
//#region ../../packages/protocol/dist/machines.js
|
|
8328
|
+
/**
|
|
8329
|
+
* Several machines for one account: what a machine says about itself (end-to-end encrypted, the
|
|
8330
|
+
* relay never reads it), the choice of a product's main bridge, and the rules that route an order.
|
|
8331
|
+
*
|
|
8332
|
+
* Generic on purpose: schemas and small pure rules only, no transport and no account.
|
|
8333
|
+
*
|
|
8334
|
+
* Reading rule: a device ignores the optional fields of a report it does not know and validates
|
|
8335
|
+
* those it knows (`MachineReportReader`); an order a machine receives stays strict.
|
|
8336
|
+
*/
|
|
8337
|
+
/** Event types of the machines, each at version 1. */
|
|
8338
|
+
const MACHINE_EVENT_TYPES = {
|
|
8339
|
+
/** The main bridge of the product changed: written by the machine that becomes main. */
|
|
8340
|
+
primaryChanged: "bridge.primary_changed" };
|
|
8341
|
+
/**
|
|
8342
|
+
* `payload` of `bridge.primary_changed`: the bridge that is now the product's main one, and the
|
|
8343
|
+
* one that was (null for the first choice). Only the last one in projection order counts.
|
|
8344
|
+
*/
|
|
8345
|
+
const PrimaryChangedPayloadSchema = z.strictObject({
|
|
8346
|
+
bridge: z.string().regex(BRIDGE_ID_PATTERN),
|
|
8347
|
+
previous: z.string().regex(BRIDGE_ID_PATTERN).nullable()
|
|
8348
|
+
});
|
|
8349
|
+
/** One AI tool the bridge detected on its machine, and how it is integrated. */
|
|
8350
|
+
const DetectedToolSchema = z.strictObject({
|
|
8351
|
+
tool: z.string().min(1).max(40),
|
|
8352
|
+
/** The integration mode, in plain words (never a path). */
|
|
8353
|
+
mode: z.string().min(1).max(60),
|
|
8354
|
+
active: z.boolean()
|
|
8355
|
+
});
|
|
8356
|
+
/**
|
|
8357
|
+
* What a bridge says of its machine, inside its end-to-end encrypted beat: its version, the
|
|
8358
|
+
* protocol version it speaks, the products it serves (names the creator knows), how many agent
|
|
8359
|
+
* instances run (an instance is an agent on a story), the tools it detected and the role the
|
|
8360
|
+
* creator gave it. The relay sees none of it.
|
|
8361
|
+
*/
|
|
8362
|
+
const MachineReportSchema = z.strictObject({
|
|
8363
|
+
/** The name the machine goes by (its host name unless the creator changed it). */
|
|
8364
|
+
name: z.string().min(1).max(60),
|
|
8365
|
+
bridge_version: SemverSchema,
|
|
8366
|
+
protocol_version: z.number().int().positive(),
|
|
8367
|
+
products: z.array(z.strictObject({
|
|
8368
|
+
id: z.string().min(1).max(80),
|
|
8369
|
+
name: z.string().min(1).max(80),
|
|
8370
|
+
/** The creator's priority for this product (1 first); absent while they gave none. */
|
|
8371
|
+
rank: z.int().min(1).max(9999).optional(),
|
|
8372
|
+
/** The marks the creator gave it (stage, pause, archive), its repository and its profile. */
|
|
8373
|
+
...ProductLifeSchema.shape
|
|
8374
|
+
})).max(64),
|
|
8375
|
+
agents: z.number().int().nonnegative(),
|
|
8376
|
+
tools: z.array(DetectedToolSchema).max(64),
|
|
8377
|
+
role: z.string().max(120).optional(),
|
|
8378
|
+
/**
|
|
8379
|
+
* The names of the tokens and logins the creator gave this machine (`forge.github`,
|
|
8380
|
+
* `data.crm`), never their values: what to renew at the platforms if the machine is lost. Only
|
|
8381
|
+
* present when there is at least one.
|
|
8382
|
+
*/
|
|
8383
|
+
secrets: z.array(z.string().regex(/^(?:forge|data)\.[a-z0-9][a-z0-9._-]{0,63}$/)).max(64).optional(),
|
|
8384
|
+
/**
|
|
8385
|
+
* The code platforms this machine is linked to (or being linked to): who, how far, until when.
|
|
8386
|
+
* Never a token. Only present when there is at least one.
|
|
8387
|
+
*/
|
|
8388
|
+
forges: z.array(ForgeLinkStatusSchema).max(16).optional(),
|
|
8389
|
+
/**
|
|
8390
|
+
* Where this machine keeps its keys: the system keychain, or the fallback store of a machine
|
|
8391
|
+
* that has none (a server). Absent from a bridge that does not say.
|
|
8392
|
+
*/
|
|
8393
|
+
key_store: z.enum(["system", "fallback"]).optional(),
|
|
8394
|
+
/**
|
|
8395
|
+
* The places this machine allows new products in, with the repositories already there that a
|
|
8396
|
+
* product can start from. Only present on a machine that can create products.
|
|
8397
|
+
*/
|
|
8398
|
+
locations: z.array(ProductLocationSchema).max(8).optional(),
|
|
8399
|
+
/** The products this machine is creating or failed to create. Only present when there is one. */
|
|
8400
|
+
creations: z.array(ProductCreationSchema).max(8).optional()
|
|
8401
|
+
});
|
|
8402
|
+
readTolerant(MachineReportSchema);
|
|
8403
|
+
z.strictObject({
|
|
8404
|
+
bridge: z.string().regex(BRIDGE_ID_PATTERN),
|
|
8405
|
+
role: z.string().max(120)
|
|
8406
|
+
});
|
|
8407
|
+
//#endregion
|
|
6770
8408
|
//#region ../../packages/protocol/dist/merge-order.js
|
|
6771
8409
|
/**
|
|
6772
8410
|
* The order of the merges and the conflicts with the main branch. The engine
|
|
@@ -6926,6 +8564,275 @@ const MethodMigratedPayloadSchema = z.strictObject({
|
|
|
6926
8564
|
to_engine: SemverSchema,
|
|
6927
8565
|
steps: z.array(MigrationStepIdSchema).max(1e3)
|
|
6928
8566
|
}).refine((payload) => payload.to_format >= payload.from_format, { path: ["to_format"] }).refine((payload) => payload.steps.length === payload.to_format - payload.from_format, { path: ["steps"] }).refine((payload) => payload.to_format !== payload.from_format || payload.from_engine !== payload.to_engine, { path: ["to_engine"] });
|
|
8567
|
+
const NoticeBaseSchema = {
|
|
8568
|
+
/** Unique in the product: the same id is never kept twice (a validation uses its order). */
|
|
8569
|
+
id: z.string().min(1).max(64),
|
|
8570
|
+
/** When the notice was made (ISO 8601). */
|
|
8571
|
+
at: z.string().min(1).max(40)
|
|
8572
|
+
};
|
|
8573
|
+
/** The creator validated (or refused, or asked changes to) a point. */
|
|
8574
|
+
const ValidatedNoticeSchema = z.strictObject({
|
|
8575
|
+
...NoticeBaseSchema,
|
|
8576
|
+
kind: z.literal("checkpoint_validated"),
|
|
8577
|
+
checkpoint: CheckpointIdSchema,
|
|
8578
|
+
decision: z.enum(CHAT_DECISIONS),
|
|
8579
|
+
option: z.string().min(1).max(32).optional(),
|
|
8580
|
+
/** The creator's own words with the decision: data for Noah, never an instruction. */
|
|
8581
|
+
note: z.string().min(1).max(MAX_WEB_ANSWER_NOTE_LENGTH).optional(),
|
|
8582
|
+
source: z.enum(ANSWER_SOURCES),
|
|
8583
|
+
story: z.string().min(1).max(32).optional()
|
|
8584
|
+
});
|
|
8585
|
+
/** A trigger the bridge carries: a recurring mission, an event, a CI run, a release. */
|
|
8586
|
+
const TriggerNoticeSchema = z.strictObject({
|
|
8587
|
+
...NoticeBaseSchema,
|
|
8588
|
+
kind: z.enum([
|
|
8589
|
+
"mission",
|
|
8590
|
+
"event",
|
|
8591
|
+
"ci",
|
|
8592
|
+
"deploy"
|
|
8593
|
+
]),
|
|
8594
|
+
/** What happened or what is due, in a few words. */
|
|
8595
|
+
subject: z.string().min(1).max(120),
|
|
8596
|
+
detail: z.string().min(1).max(300).optional()
|
|
8597
|
+
});
|
|
8598
|
+
/**
|
|
8599
|
+
* A message of the creator that Noah could not read when it came (his run did not take messages
|
|
8600
|
+
* while it went on). The words are also in the journal; they are data for Noah, quoted, and the
|
|
8601
|
+
* answer to them is the creator's request, never an instruction of the bridge.
|
|
8602
|
+
*/
|
|
8603
|
+
const RequestNoticeSchema = z.strictObject({
|
|
8604
|
+
...NoticeBaseSchema,
|
|
8605
|
+
kind: z.literal("request"),
|
|
8606
|
+
text: z.string().min(1).max(CHAIN_INSTRUCTION_MAX_CHARS),
|
|
8607
|
+
/** The message was dictated: the answer is read aloud. */
|
|
8608
|
+
voice: z.boolean().optional()
|
|
8609
|
+
});
|
|
8610
|
+
z.union([
|
|
8611
|
+
ValidatedNoticeSchema,
|
|
8612
|
+
RequestNoticeSchema,
|
|
8613
|
+
TriggerNoticeSchema
|
|
8614
|
+
]);
|
|
8615
|
+
z.strictObject({});
|
|
8616
|
+
const LiveNoticesSchema = z.strictObject({
|
|
8617
|
+
cause: z.enum([
|
|
8618
|
+
"busy",
|
|
8619
|
+
"held",
|
|
8620
|
+
"paused",
|
|
8621
|
+
"archived",
|
|
8622
|
+
"failed"
|
|
8623
|
+
]),
|
|
8624
|
+
/** Everything that waits, requests included. */
|
|
8625
|
+
count: z.number().int().min(1).max(20),
|
|
8626
|
+
/** How many of them are requests of the creator, when some are. */
|
|
8627
|
+
requests: z.number().int().min(1).max(10).optional()
|
|
8628
|
+
});
|
|
8629
|
+
//#endregion
|
|
8630
|
+
//#region ../../packages/protocol/dist/team.js
|
|
8631
|
+
const TEAM_EVENT_TYPES = {
|
|
8632
|
+
paused: "team.paused",
|
|
8633
|
+
resumed: "team.resumed"
|
|
8634
|
+
};
|
|
8635
|
+
/** `payload` of `team.paused`. */
|
|
8636
|
+
const TeamPausedPayloadSchema = z.strictObject({ reason: z.enum(["creator", "emergency_stop"]) });
|
|
8637
|
+
/** `payload` of `team.resumed`. */
|
|
8638
|
+
const TeamResumedPayloadSchema = z.strictObject({});
|
|
8639
|
+
z.strictObject({});
|
|
8640
|
+
/**
|
|
8641
|
+
* Input of `pause_team`: Noah's own id, nothing else. Noah writes the pause only once the creator
|
|
8642
|
+
* said yes in the conversation (he proposes it, he never imposes it); the resumption stays the
|
|
8643
|
+
* creator's gesture.
|
|
8644
|
+
*/
|
|
8645
|
+
const PauseTeamInputSchema = z.strictObject({ agent: AgentIdSchema.max(64) });
|
|
8646
|
+
//#endregion
|
|
8647
|
+
//#region ../../packages/protocol/dist/orders.js
|
|
8648
|
+
/**
|
|
8649
|
+
* Signed orders: every command sent to a machine is signed by the device of the creator that gives
|
|
8650
|
+
* it, and the bridge that receives it checks it with the key its journal registers for that
|
|
8651
|
+
* device, never with a key the relay supplied. An order is one JSON object; the signature covers
|
|
8652
|
+
* its canonical form (RFC 8785) without the signature itself, `t` included. `t` names the order
|
|
8653
|
+
* type and its version (`order.pair.offer/v1`), so that a signature made for an order is never
|
|
8654
|
+
* taken for an authorization, a message or an event, and conversely.
|
|
8655
|
+
*
|
|
8656
|
+
* Adding an order type means declaring its parameters, its target and its risk level in an
|
|
8657
|
+
* `OrderTypeRegistry`; no check below changes.
|
|
8658
|
+
*/
|
|
8659
|
+
/** Event type the bridge writes once an order is applied: it keeps the signed order. */
|
|
8660
|
+
const ORDER_EVENT_TYPES = {
|
|
8661
|
+
applied: "order.applied",
|
|
8662
|
+
/** A signed order the bridge refused: the journal keeps who gave it and why it was refused. */
|
|
8663
|
+
rejected: "order.rejected"
|
|
8664
|
+
};
|
|
8665
|
+
/** Orders are signed with Ed25519 only. */
|
|
8666
|
+
const ORDER_ALG = DEVICE_SIG_ALG;
|
|
8667
|
+
/** The label of the receipt a bridge signs for an order. */
|
|
8668
|
+
const ORDER_RECEIPT_TAG = "order.receipt/v1";
|
|
8669
|
+
const ORDER_TAG = /^order\.([a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)*)\/v([1-9][0-9]{0,2})$/;
|
|
8670
|
+
/** The codes a bridge answers an order with, in the order the bridge applies its checks. */
|
|
8671
|
+
const ORDER_ERROR_CODES = {
|
|
8672
|
+
INVALID_ORDER: "INVALID_ORDER",
|
|
8673
|
+
UPGRADE_REQUIRED: ErrorCode.UPGRADE_REQUIRED,
|
|
8674
|
+
UNKNOWN_DEVICE: "UNKNOWN_DEVICE",
|
|
8675
|
+
SIGNATURE_INVALID: ErrorCode.SIGNATURE_INVALID,
|
|
8676
|
+
SECURITY_ROOT_CHANGED: ErrorCode.SECURITY_ROOT_CHANGED,
|
|
8677
|
+
DEVICE_REVOKED: "DEVICE_REVOKED",
|
|
8678
|
+
WRONG_BRIDGE: "WRONG_BRIDGE",
|
|
8679
|
+
ORDER_EXPIRED: "ORDER_EXPIRED",
|
|
8680
|
+
ORDER_REPLAYED: "ORDER_REPLAYED",
|
|
8681
|
+
NOT_OWNER: "NOT_OWNER",
|
|
8682
|
+
STALE_BASIS: "STALE_BASIS",
|
|
8683
|
+
/** Two orders on the same target: the first one applied wins, the second gets this. */
|
|
8684
|
+
ALREADY_RESOLVED: "ALREADY_RESOLVED"
|
|
8685
|
+
};
|
|
8686
|
+
ORDER_ERROR_CODES.INVALID_ORDER, ORDER_ERROR_CODES.UPGRADE_REQUIRED, ORDER_ERROR_CODES.UNKNOWN_DEVICE, ORDER_ERROR_CODES.SIGNATURE_INVALID, ORDER_ERROR_CODES.SECURITY_ROOT_CHANGED, ORDER_ERROR_CODES.DEVICE_REVOKED, ORDER_ERROR_CODES.WRONG_BRIDGE, ORDER_ERROR_CODES.ORDER_EXPIRED, ORDER_ERROR_CODES.ORDER_REPLAYED, ORDER_ERROR_CODES.NOT_OWNER, ORDER_ERROR_CODES.STALE_BASIS, ORDER_ERROR_CODES.ALREADY_RESOLVED;
|
|
8687
|
+
/** `sha256:` then the digest in base64url: the fingerprint of what the creator saw. */
|
|
8688
|
+
const ORDER_BASIS_PATTERN = TRUST_EVENT_HASH_PATTERN;
|
|
8689
|
+
z.strictObject({
|
|
8690
|
+
path: z.string().min(1).max(1024),
|
|
8691
|
+
branch: z.string().min(1).max(255),
|
|
8692
|
+
blob_sha: z.string().regex(/^[0-9a-f]{40}(?:[0-9a-f]{24})?$/)
|
|
8693
|
+
});
|
|
8694
|
+
const utcDatetime = z.iso.datetime();
|
|
8695
|
+
/**
|
|
8696
|
+
* An order, as it travels. `params` is checked again by the schema of its type (a registry
|
|
8697
|
+
* declares it); here it is only a JSON object.
|
|
8698
|
+
*/
|
|
8699
|
+
const OrderSchema = z.strictObject({
|
|
8700
|
+
/** `order.<type>/v<version>`. */
|
|
8701
|
+
t: z.string().regex(ORDER_TAG),
|
|
8702
|
+
id: UlidSchema,
|
|
8703
|
+
/** The device of the creator that signs the order. */
|
|
8704
|
+
device: z.string().regex(DEVICE_ID_PATTERN),
|
|
8705
|
+
/** The bridge the order is for. */
|
|
8706
|
+
bridge: z.string().regex(BRIDGE_ID_PATTERN),
|
|
8707
|
+
product: UlidSchema,
|
|
8708
|
+
/** The story the order is about, or null. */
|
|
8709
|
+
target: z.string().min(1).max(120).nullable(),
|
|
8710
|
+
/** The fingerprint of the state and documents the creator saw, or null. */
|
|
8711
|
+
basis: z.string().regex(ORDER_BASIS_PATTERN).nullable(),
|
|
8712
|
+
/** The device's own counter: strictly increasing, one per order the device signs. */
|
|
8713
|
+
seq: z.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
|
|
8714
|
+
issued_at: utcDatetime,
|
|
8715
|
+
expires_at: utcDatetime,
|
|
8716
|
+
params: z.record(z.string(), z.unknown()),
|
|
8717
|
+
alg: z.literal(ORDER_ALG),
|
|
8718
|
+
sig: Ed25519SignatureSchema
|
|
8719
|
+
}).refine((order) => Date.parse(order.expires_at) > Date.parse(order.issued_at), {
|
|
8720
|
+
path: ["expires_at"],
|
|
8721
|
+
message: "an order expires after it is issued"
|
|
8722
|
+
}).refine((order) => jsonValueIssue(order.params) === void 0, {
|
|
8723
|
+
path: ["params"],
|
|
8724
|
+
message: "params are JSON"
|
|
8725
|
+
});
|
|
8726
|
+
z.strictObject({
|
|
8727
|
+
t: z.literal(ORDER_RECEIPT_TAG),
|
|
8728
|
+
order: UlidSchema,
|
|
8729
|
+
/** The device that gave the order: the receipt is for it only. */
|
|
8730
|
+
device: z.string().regex(DEVICE_ID_PATTERN),
|
|
8731
|
+
bridge: z.string().regex(BRIDGE_ID_PATTERN),
|
|
8732
|
+
product: UlidSchema,
|
|
8733
|
+
outcome: z.enum(["applied", "rejected"]),
|
|
8734
|
+
/** The code the emitting device sees (never `UNKNOWN_DEVICE`); present when rejected. */
|
|
8735
|
+
code: z.string().regex(/^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$/).optional(),
|
|
8736
|
+
/**
|
|
8737
|
+
* A simple handle on what an applied order made (never its content): for a secret, the
|
|
8738
|
+
* reference under which the machine keeps it.
|
|
8739
|
+
*/
|
|
8740
|
+
reference: z.string().min(1).max(128).optional(),
|
|
8741
|
+
at: utcDatetime,
|
|
8742
|
+
alg: z.literal(ORDER_ALG),
|
|
8743
|
+
sig: Ed25519SignatureSchema
|
|
8744
|
+
}).refine((receipt) => receipt.outcome === "rejected" === (receipt.code !== void 0), {
|
|
8745
|
+
path: ["code"],
|
|
8746
|
+
message: "a rejected receipt names its code, an applied one has none"
|
|
8747
|
+
}).refine((receipt) => receipt.reference === void 0 || receipt.outcome === "applied", {
|
|
8748
|
+
path: ["reference"],
|
|
8749
|
+
message: "only an applied receipt carries a reference"
|
|
8750
|
+
}).refine((receipt) => receipt.code !== ORDER_ERROR_CODES.UNKNOWN_DEVICE, {
|
|
8751
|
+
path: ["code"],
|
|
8752
|
+
message: "the external code never tells that a device is unknown"
|
|
8753
|
+
});
|
|
8754
|
+
/** Where an order was given from, as the bridge knows it from the device's kind in its registry. */
|
|
8755
|
+
const ORDER_SOURCES = [
|
|
8756
|
+
"web",
|
|
8757
|
+
"mobile",
|
|
8758
|
+
"machine"
|
|
8759
|
+
];
|
|
8760
|
+
/** `payload` of `order.applied` v1: the signed order, kept so that anyone can check it without the bridge. */
|
|
8761
|
+
const OrderAppliedPayloadSchema = z.strictObject({
|
|
8762
|
+
order: OrderSchema,
|
|
8763
|
+
/** The emitting device (the order's own `device`). */
|
|
8764
|
+
device: z.string().regex(DEVICE_ID_PATTERN),
|
|
8765
|
+
/** Fixed by the way in (the kind of device in the registry), never read from the order. */
|
|
8766
|
+
source: z.enum(ORDER_SOURCES),
|
|
8767
|
+
applied_at: utcDatetime
|
|
8768
|
+
});
|
|
8769
|
+
/**
|
|
8770
|
+
* `payload` of `order.rejected` v1: an order whose signature checked out and that the bridge did
|
|
8771
|
+
* not apply, with the code the creator sees (never `UNKNOWN_DEVICE`). It carries no `params`: a
|
|
8772
|
+
* refusal is a fact, not a second copy of the order.
|
|
8773
|
+
*/
|
|
8774
|
+
const OrderRejectedPayloadSchema = z.strictObject({
|
|
8775
|
+
order: UlidSchema,
|
|
8776
|
+
/** `order.<type>/v<version>`. */
|
|
8777
|
+
t: z.string().regex(ORDER_TAG),
|
|
8778
|
+
device: z.string().regex(DEVICE_ID_PATTERN),
|
|
8779
|
+
code: z.string().regex(/^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$/),
|
|
8780
|
+
source: z.enum(ORDER_SOURCES),
|
|
8781
|
+
rejected_at: utcDatetime
|
|
8782
|
+
});
|
|
8783
|
+
/**
|
|
8784
|
+
* A link or a QR code for one more browser or phone: the rendezvous and the secret the device drew,
|
|
8785
|
+
* and the device's own signature of the authorization the bridge will write `device.paired` on
|
|
8786
|
+
* (`offer_sig`, over the `pair.offer` record of the kind of device, this bridge, `x` and `expires_at`).
|
|
8787
|
+
*/
|
|
8788
|
+
const PairOfferLinkParamsSchema = z.strictObject({
|
|
8789
|
+
kind: z.enum(["link", "phone"]),
|
|
8790
|
+
x: z.string().length(22).refine(isCanonicalBase64url),
|
|
8791
|
+
s: z.string().length(22).refine(isCanonicalBase64url),
|
|
8792
|
+
expires_at: utcDatetime,
|
|
8793
|
+
offer_sig: Ed25519SignatureSchema
|
|
8794
|
+
});
|
|
8795
|
+
/** The short code of another machine: an address of 8 symbols and a password of 6. */
|
|
8796
|
+
const PairOfferCodeParamsSchema = z.strictObject({
|
|
8797
|
+
kind: z.literal("machine"),
|
|
8798
|
+
x: z.string().regex(/^[0-9A-HJKMNP-TV-Z]{8}$/),
|
|
8799
|
+
w: z.string().regex(/^[0-9A-HJKMNP-TV-Z]{6}$/),
|
|
8800
|
+
expires_at: utcDatetime,
|
|
8801
|
+
offer_sig: Ed25519SignatureSchema
|
|
8802
|
+
});
|
|
8803
|
+
z.union([PairOfferLinkParamsSchema, PairOfferCodeParamsSchema]);
|
|
8804
|
+
z.strictObject({ heads: z.record(z.string().regex(BRIDGE_ID_PATTERN), z.string().regex(TRUST_EVENT_HASH_PATTERN)).refine((heads) => Object.keys(heads).length <= 64, { message: "at most 64 writers" }) });
|
|
8805
|
+
z.strictObject({ role: z.string().max(120) });
|
|
8806
|
+
z.strictObject({});
|
|
8807
|
+
/** The kinds of connection a secret belongs to, and the shape of its name (`forge.github`, `data.crm`). */
|
|
8808
|
+
const SECRET_NAME_PATTERN = /^(?:forge|data)\.[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
8809
|
+
/** The longest sealed box of a secret, as text: the secret in UTF-8 (4 bytes a character), its envelope and the box's own. */
|
|
8810
|
+
const SEALED_SECRET_MAX_CHARS = Math.ceil(262784 / 3) + 80;
|
|
8811
|
+
z.strictObject({
|
|
8812
|
+
name: z.string().regex(SECRET_NAME_PATTERN),
|
|
8813
|
+
sealed: z.string().min(1).max(SEALED_SECRET_MAX_CHARS).regex(/^[A-Za-z0-9_-]+$/)
|
|
8814
|
+
});
|
|
8815
|
+
z.strictObject({ ranks: z.record(UlidSchema, z.int().min(1).max(9999)).refine((all) => Object.keys(all).length <= 64, { message: "at most 64 products" }) });
|
|
8816
|
+
/**
|
|
8817
|
+
* The creator removes a device (a phone, a browser or a machine, possibly the one that gives the
|
|
8818
|
+
* order). The device signs the `device.revoked` authorization the bridge will write on its
|
|
8819
|
+
* authority (over the record of the removed device, the head of its chain, the date and the
|
|
8820
|
+
* author, which is the device that gives the order), so that the journal proves who removed what
|
|
8821
|
+
* without the bridge's word. `chain_head` is the last event of a removed machine's chain the author
|
|
8822
|
+
* saw in a product's journal (each journal has its own chain), null for a phone or a browser.
|
|
8823
|
+
* `authorizations` holds one signature per product the author saw the machine in, and `*` for any
|
|
8824
|
+
* other product (always with a null head). The order is for the bridge that applies it, which
|
|
8825
|
+
* writes the removal in every product it serves.
|
|
8826
|
+
*/
|
|
8827
|
+
const RevocationAuthorizationSchema = z.strictObject({
|
|
8828
|
+
chain_head: z.string().regex(TRUST_EVENT_HASH_PATTERN).nullable(),
|
|
8829
|
+
sig: Ed25519SignatureSchema
|
|
8830
|
+
});
|
|
8831
|
+
z.strictObject({
|
|
8832
|
+
device: z.string().regex(PAIRED_DEVICE_ID_PATTERN),
|
|
8833
|
+
issued_at: utcDatetime,
|
|
8834
|
+
authorizations: z.record(z.string().refine((key) => key === "*" || ULID_PATTERN.test(key), { message: "a product identifier or *" }), RevocationAuthorizationSchema).refine((all) => "*" in all && all["*"]?.chain_head === null, { message: "the authorization for any product has no chain head" }).refine((all) => Object.keys(all).length <= 65, { message: "at most 64 products" })
|
|
8835
|
+
});
|
|
6929
8836
|
//#endregion
|
|
6930
8837
|
//#region ../../packages/protocol/dist/product-key.js
|
|
6931
8838
|
/**
|
|
@@ -6936,12 +8843,21 @@ const MethodMigratedPayloadSchema = z.strictObject({
|
|
|
6936
8843
|
* written: by `product.key_created` in the journal, and in each encrypted value.
|
|
6937
8844
|
*/
|
|
6938
8845
|
/** Event types of the product key, each at version 1. */
|
|
6939
|
-
const PRODUCT_KEY_EVENT_TYPES = {
|
|
6940
|
-
/** The product key was created: its `key_id`, signed by the machine that created it. */
|
|
6941
|
-
keyCreated: "product.key_created"
|
|
8846
|
+
const PRODUCT_KEY_EVENT_TYPES = {
|
|
8847
|
+
/** The product key was created: its `key_id`, signed by the machine that created it. */
|
|
8848
|
+
keyCreated: "product.key_created",
|
|
8849
|
+
/**
|
|
8850
|
+
* The product key was renewed: a new key follows the one named by `previous_key_id`. The old key
|
|
8851
|
+
* stays valid for reading everything written before; only new content is written under the new
|
|
8852
|
+
* one. Each trusted machine gets the new key in a box sealed for its own encryption key.
|
|
8853
|
+
*/
|
|
8854
|
+
keyRotated: "product.key_rotated"
|
|
8855
|
+
};
|
|
6942
8856
|
/** `pk:` then the first 16 base64url characters of the key's `apkeyid1` subkey (`crypto_kdf`). */
|
|
6943
8857
|
const KEY_ID_PATTERN = /^pk:[A-Za-z0-9_-]{16}$/;
|
|
6944
8858
|
const KeyIdSchema = z.string().regex(KEY_ID_PATTERN);
|
|
8859
|
+
/** 32 bytes in base64url: 43 characters, the last one carrying 4 bits only. */
|
|
8860
|
+
const PROOF_PATTERN = /^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/;
|
|
6945
8861
|
/**
|
|
6946
8862
|
* `payload` of `product.key_created`: the `key_id`, and the `proof` that its writer held the key
|
|
6947
8863
|
* (keyed BLAKE2b of `agentplugged/key-proof/v1\0` + the product id, by the key's `approof1`
|
|
@@ -6949,7 +8865,33 @@ const KeyIdSchema = z.string().regex(KEY_ID_PATTERN);
|
|
|
6949
8865
|
*/
|
|
6950
8866
|
const ProductKeyCreatedPayloadSchema = z.strictObject({
|
|
6951
8867
|
key_id: KeyIdSchema,
|
|
6952
|
-
proof: z.string().regex(
|
|
8868
|
+
proof: z.string().regex(PROOF_PATTERN)
|
|
8869
|
+
});
|
|
8870
|
+
/** A trusted machine's identifier, the name of its box in a renewal. */
|
|
8871
|
+
const MACHINE_BOX_OWNER = /^machine:[A-Za-z0-9_-]{16}$/;
|
|
8872
|
+
/** A sealed box (libsodium `crypto_box_seal`) in base64url: at most 256 characters. */
|
|
8873
|
+
const KEY_BOX = z.string().regex(/^[A-Za-z0-9_-]{60,256}$/);
|
|
8874
|
+
/**
|
|
8875
|
+
* `payload` of `product.key_rotated`: the new `key_id`, the key it follows, the `proof` that the
|
|
8876
|
+
* writer held the new key (the same proof as the first announcement), and `boxes`: the new key
|
|
8877
|
+
* sealed, one box per trusted machine, for that machine's encryption key. No key is ever in clear.
|
|
8878
|
+
*/
|
|
8879
|
+
const ProductKeyRotatedPayloadSchema = z.strictObject({
|
|
8880
|
+
key_id: KeyIdSchema,
|
|
8881
|
+
previous_key_id: KeyIdSchema,
|
|
8882
|
+
proof: z.string().regex(PROOF_PATTERN),
|
|
8883
|
+
/**
|
|
8884
|
+
* The proof that the writer held the key this one follows (made with that key, for this renewal
|
|
8885
|
+
* only): a machine that holds the previous key ignores a renewal that does not carry it.
|
|
8886
|
+
*/
|
|
8887
|
+
chain_proof: z.string().regex(PROOF_PATTERN),
|
|
8888
|
+
/**
|
|
8889
|
+
* The signed order that asked for the renewal, when it came from a creator's device: the
|
|
8890
|
+
* `order.applied` of the same journal keeps that order. A renewal run again for the same order
|
|
8891
|
+
* is recognised by it and not made twice.
|
|
8892
|
+
*/
|
|
8893
|
+
order: z.string().regex(/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/).optional(),
|
|
8894
|
+
boxes: z.record(z.string().regex(MACHINE_BOX_OWNER), KEY_BOX).refine((boxes) => Object.keys(boxes).length <= 64, { message: `at most 64 boxes` })
|
|
6953
8895
|
});
|
|
6954
8896
|
/** The algorithm of `payload_enc` and of the sensitive files: XChaCha20-Poly1305 (IETF). */
|
|
6955
8897
|
const PAYLOAD_ENCRYPTION_ALG = "xchacha20poly1305";
|
|
@@ -6970,6 +8912,8 @@ const MAX_SENSITIVE_CONTENT_BYTES = 262144;
|
|
|
6970
8912
|
const ENCRYPTED_FILE_PREFIX = "agentplugged-enc:1:";
|
|
6971
8913
|
/** Prefix of an exported product key (`key export`). */
|
|
6972
8914
|
const PRODUCT_KEY_EXPORT_PREFIX = "apk1:";
|
|
8915
|
+
/** Prefix of an export that holds several keys: the current one first, then the older ones. */
|
|
8916
|
+
const PRODUCT_KEYS_EXPORT_PREFIX = "apk2:";
|
|
6973
8917
|
/** Why a path given to a sensitive file tool is refused. */
|
|
6974
8918
|
const PATH_MESSAGE = "expected a relative path inside the area (`/` between names; no `..`, `.`, empty name, drive, reserved character or Windows device name; no name ending with a dot or a space)";
|
|
6975
8919
|
/** A file name part: no control character, no character Windows refuses, not `.` or `..`. */
|
|
@@ -7043,8 +8987,8 @@ const READINESS_GESTURES = ["fix", "accept"];
|
|
|
7043
8987
|
/** The option of the decision a blocking verdict opens by which the creator goes on all the same. */
|
|
7044
8988
|
const READINESS_GO_ON = "go_on";
|
|
7045
8989
|
const READINESS_FIX_FIRST = "fix_first";
|
|
7046
|
-
const NO_CONTROL$
|
|
7047
|
-
const lineText$2 = (max) => z.string().trim().min(1).max(max).refine((text) => !/[\r\n]/.test(text), { message: "expected a single line" }).refine((text) => !hasControlCharacter(text, false), NO_CONTROL$
|
|
8990
|
+
const NO_CONTROL$1 = { message: "expected no control character" };
|
|
8991
|
+
const lineText$2 = (max) => z.string().trim().min(1).max(max).refine((text) => !/[\r\n]/.test(text), { message: "expected a single line" }).refine((text) => !hasControlCharacter(text, false), NO_CONTROL$1);
|
|
7048
8992
|
/**
|
|
7049
8993
|
* One judged finding as the report gives it. `passages` are `<document>: <passage>`: the document
|
|
7050
8994
|
* by its id or its file name, then a heading or an identifier (`EF-n`, `ENF-n`, `PU-n`, `AD-n`) it
|
|
@@ -7395,8 +9339,8 @@ const REGULATED_FIELDS = [
|
|
|
7395
9339
|
"health",
|
|
7396
9340
|
"finance"
|
|
7397
9341
|
];
|
|
7398
|
-
const NO_CONTROL
|
|
7399
|
-
const lineText$1 = (max) => z.string().trim().min(1).max(max).refine((text) => !/[\r\n]/.test(text), { message: "expected a single line" }).refine((text) => !hasControlCharacter(text, false), NO_CONTROL
|
|
9342
|
+
const NO_CONTROL = { message: "expected no control character" };
|
|
9343
|
+
const lineText$1 = (max) => z.string().trim().min(1).max(max).refine((text) => !/[\r\n]/.test(text), { message: "expected a single line" }).refine((text) => !hasControlCharacter(text, false), NO_CONTROL);
|
|
7400
9344
|
/** A guest's identifier: `guest-` and a slug, so it is never mistaken for an agent of the team. */
|
|
7401
9345
|
const GuestIdSchema = z.string().max(48).regex(/^guest-[a-z0-9]+(?:-[a-z0-9]+)*$/);
|
|
7402
9346
|
/** A saved room's identifier: the name of its files. */
|
|
@@ -7777,109 +9721,30 @@ const TestsReviewedPayloadSchema = z.strictObject({
|
|
|
7777
9721
|
commit: CommitIdSchema.optional(),
|
|
7778
9722
|
score: z.number().int().min(0).max(100).optional(),
|
|
7779
9723
|
grade: z.enum([
|
|
7780
|
-
"A",
|
|
7781
|
-
"B",
|
|
7782
|
-
"C",
|
|
7783
|
-
"D",
|
|
7784
|
-
"F"
|
|
7785
|
-
]).optional(),
|
|
7786
|
-
verdict: z.enum([
|
|
7787
|
-
"sound",
|
|
7788
|
-
"to_improve",
|
|
7789
|
-
"to_fix",
|
|
7790
|
-
"blocked"
|
|
7791
|
-
]).optional(),
|
|
7792
|
-
criteria: z.array(z.strictObject({
|
|
7793
|
-
criterion: z.enum(TEST_CRITERIA),
|
|
7794
|
-
score: z.number().int().min(0).max(100)
|
|
7795
|
-
})).max(TEST_CRITERIA.length).optional(),
|
|
7796
|
-
findings: z.number().int().min(0).max(60).optional(),
|
|
7797
|
-
mutation: z.strictObject({
|
|
7798
|
-
ran: z.number().int().min(0).max(30),
|
|
7799
|
-
killed: z.number().int().min(0).max(30),
|
|
7800
|
-
survived: z.number().int().min(0).max(30)
|
|
7801
|
-
}).optional(),
|
|
7802
|
-
gaps: z.number().int().min(0).max(1e3)
|
|
7803
|
-
});
|
|
7804
|
-
//#endregion
|
|
7805
|
-
//#region ../../packages/protocol/dist/trace.js
|
|
7806
|
-
/**
|
|
7807
|
-
* Traceability and deviations. Each requirement of the PRD (`EF-n`, `ENF-n`) is linked,
|
|
7808
|
-
* at read time, to the stories that cite it, their pull requests, the tests that cite it and the
|
|
7809
|
-
* deviations recorded on it: nothing of that link is ever written in a document. A deviation is a
|
|
7810
|
-
* signed event of the journal: an agent (the QA reviewer) found the delivered work drifting from
|
|
7811
|
-
* requirements a started story cites. Its id, like an item of deferred work's, is `V-` then the
|
|
7812
|
-
* last 10 characters of the ULID of its event, never written in the `payload`, never reused.
|
|
7813
|
-
*/
|
|
7814
|
-
/** Event types of the deviations, each at version 1. */
|
|
7815
|
-
const DEVIATION_EVENT_TYPES = {
|
|
7816
|
-
reported: "deviation.reported",
|
|
7817
|
-
resolved: "deviation.resolved"
|
|
7818
|
-
};
|
|
7819
|
-
/** Where a requirement stands: to do, in development, in review, delivered, or deviating. */
|
|
7820
|
-
const REQUIREMENT_STATUSES = [
|
|
7821
|
-
"todo",
|
|
7822
|
-
"in_development",
|
|
7823
|
-
"in_review",
|
|
7824
|
-
"delivered",
|
|
7825
|
-
"deviation"
|
|
7826
|
-
];
|
|
7827
|
-
/** The requirements traced: the functional and non-functional ones of the PRD. */
|
|
7828
|
-
const TRACED_REQUIREMENT_PATTERN = /^(?:EF|ENF)-[1-9]\d*$/;
|
|
7829
|
-
/** A traced requirement as typed (`ef-18`, with spaces): trimmed, in upper case, then checked. */
|
|
7830
|
-
const TracedRequirementInputSchema = z.string().trim().toUpperCase().pipe(z.string().regex(TRACED_REQUIREMENT_PATTERN, { message: "expected EF-n or ENF-n, for example EF-18" }));
|
|
7831
|
-
/** `V-` then the last 10 characters of the ULID of the event that reported the deviation. */
|
|
7832
|
-
const DeviationIdSchema = z.string().regex(/^V-[0-9A-HJKMNP-TV-Z]{10}$/, { message: "expected V- then 10 characters, for example V-7M2K9QX4TB" });
|
|
7833
|
-
/** An id as typed (`v-7m2k9qx4tb`, with spaces): trimmed and in upper case, then checked. */
|
|
7834
|
-
const DeviationIdInputSchema = z.string().trim().toUpperCase().pipe(DeviationIdSchema);
|
|
7835
|
-
/** The id of the deviation reported by the event `eventId`: stable, never renumbered. */
|
|
7836
|
-
function deviationIdOf(eventId) {
|
|
7837
|
-
return `V-${eventId.slice(-10)}`;
|
|
7838
|
-
}
|
|
7839
|
-
const NO_CONTROL = { message: "expected no control character" };
|
|
7840
|
-
const SummarySchema = z.string().trim().min(1).max(200).refine((summary) => !/[\r\n]/.test(summary), { message: "expected a single line" }).refine((summary) => !hasControlCharacter(summary, false), NO_CONTROL);
|
|
7841
|
-
const DetailSchema = z.string().trim().min(1).max(2e3).refine((detail) => !hasControlCharacter(detail, true), NO_CONTROL);
|
|
7842
|
-
const ReasonSchema = z.string().trim().min(1).max(1e3).refine((reason) => !hasControlCharacter(reason, true), NO_CONTROL);
|
|
7843
|
-
const RequirementIdSchema = z.string().regex(REQUIREMENT_ID_PATTERN, { message: "expected a requirement id (EF-n, ENF-n, PU-n, MS-n or AD-n)" });
|
|
7844
|
-
const requirementsOf = (id) => z.array(id).min(1).max(20).refine((ids) => new Set(ids).size === ids.length, { message: "requirements must differ" });
|
|
7845
|
-
/**
|
|
7846
|
-
* `payload` of `deviation.reported`. The story is the envelope's (required); `pr` is the number of
|
|
7847
|
-
* the story's pull request when the deviation was reported, set by the engine from the journal.
|
|
7848
|
-
*/
|
|
7849
|
-
const DeviationReportedPayloadSchema = z.strictObject({
|
|
7850
|
-
requirements: requirementsOf(RequirementIdSchema),
|
|
7851
|
-
summary: SummarySchema,
|
|
7852
|
-
detail: DetailSchema.optional(),
|
|
7853
|
-
pr: z.number().int().min(1).max(Number.MAX_SAFE_INTEGER).optional(),
|
|
7854
|
-
/** The commit compared with the requirements. */
|
|
7855
|
-
commit: CommitIdSchema.optional()
|
|
7856
|
-
});
|
|
7857
|
-
/** `payload` of `deviation.resolved`: the deviation, and how it was fixed or why it is lifted. */
|
|
7858
|
-
const DeviationResolvedPayloadSchema = z.strictObject({
|
|
7859
|
-
deviation: DeviationIdSchema,
|
|
7860
|
-
reason: ReasonSchema
|
|
7861
|
-
});
|
|
7862
|
-
/** A requirement id as typed: in upper case, then checked. */
|
|
7863
|
-
const RequirementInputSchema = z.string().trim().toUpperCase().pipe(RequirementIdSchema);
|
|
7864
|
-
/** Input of `report_deviation`: the story brought to its canonical form. */
|
|
7865
|
-
const ReportDeviationInputSchema = z.strictObject({
|
|
7866
|
-
agent: AgentIdSchema.max(64),
|
|
7867
|
-
story: StoryIdSchema.overwrite(canonicalStoryId),
|
|
7868
|
-
requirements: requirementsOf(RequirementInputSchema),
|
|
7869
|
-
summary: SummarySchema,
|
|
7870
|
-
detail: DetailSchema.optional(),
|
|
7871
|
-
commit: CommitIdSchema.optional()
|
|
7872
|
-
});
|
|
7873
|
-
/** Input of `lift_deviation`: the agent that reported it lifts it, with why. */
|
|
7874
|
-
const ResolveDeviationInputSchema = z.strictObject({
|
|
7875
|
-
agent: AgentIdSchema.max(64),
|
|
7876
|
-
deviation: DeviationIdInputSchema,
|
|
7877
|
-
reason: ReasonSchema
|
|
9724
|
+
"A",
|
|
9725
|
+
"B",
|
|
9726
|
+
"C",
|
|
9727
|
+
"D",
|
|
9728
|
+
"F"
|
|
9729
|
+
]).optional(),
|
|
9730
|
+
verdict: z.enum([
|
|
9731
|
+
"sound",
|
|
9732
|
+
"to_improve",
|
|
9733
|
+
"to_fix",
|
|
9734
|
+
"blocked"
|
|
9735
|
+
]).optional(),
|
|
9736
|
+
criteria: z.array(z.strictObject({
|
|
9737
|
+
criterion: z.enum(TEST_CRITERIA),
|
|
9738
|
+
score: z.number().int().min(0).max(100)
|
|
9739
|
+
})).max(TEST_CRITERIA.length).optional(),
|
|
9740
|
+
findings: z.number().int().min(0).max(60).optional(),
|
|
9741
|
+
mutation: z.strictObject({
|
|
9742
|
+
ran: z.number().int().min(0).max(30),
|
|
9743
|
+
killed: z.number().int().min(0).max(30),
|
|
9744
|
+
survived: z.number().int().min(0).max(30)
|
|
9745
|
+
}).optional(),
|
|
9746
|
+
gaps: z.number().int().min(0).max(1e3)
|
|
7878
9747
|
});
|
|
7879
|
-
/** The reason the creator gives at the terminal (`trace resolve`). */
|
|
7880
|
-
const DeviationReasonSchema = ReasonSchema;
|
|
7881
|
-
/** Input of `get_trace`: one requirement, or every one when absent. */
|
|
7882
|
-
const GetTraceInputSchema = z.strictObject({ requirement: z.string().max(32).optional() });
|
|
7883
9748
|
//#endregion
|
|
7884
9749
|
//#region ../../packages/protocol/dist/usage.js
|
|
7885
9750
|
/**
|
|
@@ -8030,6 +9895,26 @@ const EVENT_TYPE_REGISTRY = {
|
|
|
8030
9895
|
version: currentVersionOf(AGENT_EVENT_TYPES.statusReported),
|
|
8031
9896
|
payload: StatusReportedPayloadSchema
|
|
8032
9897
|
},
|
|
9898
|
+
/** An instruction of the creator to an agent: their own words, encrypted with the product key. */
|
|
9899
|
+
[CHAIN_EVENT_TYPES.instructionGiven]: {
|
|
9900
|
+
version: 1,
|
|
9901
|
+
payload: InstructionGivenPayloadSchema,
|
|
9902
|
+
sensitive: true
|
|
9903
|
+
},
|
|
9904
|
+
/** The creator holds the whole team, then lets it go on. */
|
|
9905
|
+
[TEAM_EVENT_TYPES.paused]: {
|
|
9906
|
+
version: 1,
|
|
9907
|
+
payload: TeamPausedPayloadSchema
|
|
9908
|
+
},
|
|
9909
|
+
[TEAM_EVENT_TYPES.resumed]: {
|
|
9910
|
+
version: 1,
|
|
9911
|
+
payload: TeamResumedPayloadSchema
|
|
9912
|
+
},
|
|
9913
|
+
/** Noah's short note at the end of an exchange with the creator. */
|
|
9914
|
+
[EXCHANGE_EVENT_TYPES.summarized]: {
|
|
9915
|
+
version: 1,
|
|
9916
|
+
payload: ExchangeSummarizedPayloadSchema
|
|
9917
|
+
},
|
|
8033
9918
|
[AGENT_EVENT_TYPES.progressReported]: {
|
|
8034
9919
|
version: 1,
|
|
8035
9920
|
payload: ProgressReportedPayloadSchema
|
|
@@ -8075,6 +9960,10 @@ const EVENT_TYPE_REGISTRY = {
|
|
|
8075
9960
|
version: 1,
|
|
8076
9961
|
payload: ProductKeyCreatedPayloadSchema
|
|
8077
9962
|
},
|
|
9963
|
+
[PRODUCT_KEY_EVENT_TYPES.keyRotated]: {
|
|
9964
|
+
version: 1,
|
|
9965
|
+
payload: ProductKeyRotatedPayloadSchema
|
|
9966
|
+
},
|
|
8078
9967
|
/**
|
|
8079
9968
|
* The registry of trusted devices: a device paired, revoked, confirmed, the
|
|
8080
9969
|
* root certified by the first phone, and an alert dismissed by the creator. Clear: every
|
|
@@ -8100,6 +9989,21 @@ const EVENT_TYPE_REGISTRY = {
|
|
|
8100
9989
|
version: 1,
|
|
8101
9990
|
payload: TrustDismissedPayloadSchema
|
|
8102
9991
|
},
|
|
9992
|
+
/** The main bridge of the product, chosen by the creator. */
|
|
9993
|
+
[MACHINE_EVENT_TYPES.primaryChanged]: {
|
|
9994
|
+
version: 1,
|
|
9995
|
+
payload: PrimaryChangedPayloadSchema
|
|
9996
|
+
},
|
|
9997
|
+
/** An order a bridge applied, with the creator's signed order kept as the proof. */
|
|
9998
|
+
[ORDER_EVENT_TYPES.applied]: {
|
|
9999
|
+
version: 1,
|
|
10000
|
+
payload: OrderAppliedPayloadSchema
|
|
10001
|
+
},
|
|
10002
|
+
/** A signed order a bridge refused, with the code the creator saw. */
|
|
10003
|
+
[ORDER_EVENT_TYPES.rejected]: {
|
|
10004
|
+
version: 1,
|
|
10005
|
+
payload: OrderRejectedPayloadSchema
|
|
10006
|
+
},
|
|
8103
10007
|
/** Reserved to tests: any payload. */
|
|
8104
10008
|
[TEST_EVENT_TYPE]: {
|
|
8105
10009
|
version: 1,
|
|
@@ -8310,6 +10214,26 @@ const EVENT_TYPE_REGISTRY = {
|
|
|
8310
10214
|
version: currentVersionOf(ELICITATION_EVENT_TYPES.answered),
|
|
8311
10215
|
payload: CheckpointElicitedPayloadSchema
|
|
8312
10216
|
},
|
|
10217
|
+
/** The creator's answer carried by the signed order of a browser (the web view). */
|
|
10218
|
+
[WEB_ANSWER_EVENT_TYPES.answered]: {
|
|
10219
|
+
version: currentVersionOf(WEB_ANSWER_EVENT_TYPES.answered),
|
|
10220
|
+
payload: CheckpointWebAnsweredPayloadSchema
|
|
10221
|
+
},
|
|
10222
|
+
/** An answer from the web held for the undo window: the point stays open meanwhile. */
|
|
10223
|
+
[WEB_ANSWER_EVENT_TYPES.held]: {
|
|
10224
|
+
version: currentVersionOf(WEB_ANSWER_EVENT_TYPES.held),
|
|
10225
|
+
payload: CheckpointWebHeldPayloadSchema
|
|
10226
|
+
},
|
|
10227
|
+
/** A held answer taken back by the creator: the point was never answered. */
|
|
10228
|
+
[WEB_ANSWER_EVENT_TYPES.holdCancelled]: {
|
|
10229
|
+
version: 1,
|
|
10230
|
+
payload: CheckpointWebHoldCancelledPayloadSchema
|
|
10231
|
+
},
|
|
10232
|
+
/** A held answer the machine could not carry out: released, the point is open again and says why. */
|
|
10233
|
+
[WEB_ANSWER_EVENT_TYPES.holdFailed]: {
|
|
10234
|
+
version: 1,
|
|
10235
|
+
payload: CheckpointWebHoldFailedPayloadSchema
|
|
10236
|
+
},
|
|
8313
10237
|
/** A story merged by the engine once the creator approved its merge checkpoint. */
|
|
8314
10238
|
[MERGE_EVENT_TYPES.merged]: {
|
|
8315
10239
|
version: currentVersionOf(MERGE_EVENT_TYPES.merged),
|
|
@@ -8417,6 +10341,7 @@ function isTestEventType(type) {
|
|
|
8417
10341
|
* a sensitive domain (`salle.*`, `security.*`, `memory.*`), known yet or not.
|
|
8418
10342
|
*/
|
|
8419
10343
|
function isSensitiveEventType(type) {
|
|
10344
|
+
if (isExtensionEventType(type)) return true;
|
|
8420
10345
|
if (eventTypeSpec(type)?.sensitive === true) return true;
|
|
8421
10346
|
const domain = type.split(".")[0] ?? "";
|
|
8422
10347
|
return SENSITIVE_EVENT_DOMAINS.includes(domain);
|
|
@@ -8431,6 +10356,7 @@ function eventTypeSpec(type) {
|
|
|
8431
10356
|
* undefined when it can read it.
|
|
8432
10357
|
*/
|
|
8433
10358
|
function eventTypeUpgrade(type, version) {
|
|
10359
|
+
if (isExtensionEventType(type)) return void 0;
|
|
8434
10360
|
const spec = eventTypeSpec(type);
|
|
8435
10361
|
if (spec !== void 0 && version <= spec.version) return void 0;
|
|
8436
10362
|
return {
|
|
@@ -8455,6 +10381,10 @@ function currentEvent(event) {
|
|
|
8455
10381
|
ok: false,
|
|
8456
10382
|
error: upgrade
|
|
8457
10383
|
};
|
|
10384
|
+
if (isExtensionEventType(event.type)) return {
|
|
10385
|
+
ok: true,
|
|
10386
|
+
event
|
|
10387
|
+
};
|
|
8458
10388
|
const spec = eventTypeSpec(event.type);
|
|
8459
10389
|
let current = event;
|
|
8460
10390
|
for (let version = event.v; version < spec.version; version++) {
|
|
@@ -8481,6 +10411,437 @@ function currentEvent(event) {
|
|
|
8481
10411
|
event: current
|
|
8482
10412
|
};
|
|
8483
10413
|
}
|
|
10414
|
+
/** The request shape (without the identifier, the version and the product, which the file reading adds). */
|
|
10415
|
+
const FindRequestFields = {
|
|
10416
|
+
op: z.literal("find"),
|
|
10417
|
+
query: z.string().min(1).max(120)
|
|
10418
|
+
};
|
|
10419
|
+
/** A place in a document where the words are: the document, its section, the line and the words around. */
|
|
10420
|
+
const FindHitSchema = z.strictObject({
|
|
10421
|
+
/** The path from the product's folder, with `/`. */
|
|
10422
|
+
path: z.string().min(1).max(300),
|
|
10423
|
+
/** True for a page of the knowledge base (it opens in the knowledge base, not in the documents). */
|
|
10424
|
+
kb: z.literal(true).optional(),
|
|
10425
|
+
title: z.string().min(1).max(200),
|
|
10426
|
+
/** The heading of the section, when it has one. */
|
|
10427
|
+
heading: z.string().max(200).optional(),
|
|
10428
|
+
/** The line of the document where the words are, from 1. */
|
|
10429
|
+
line: z.int().min(1).max(1e7),
|
|
10430
|
+
/** The line itself, cut around the words. */
|
|
10431
|
+
snippet: z.string().min(1).max(240)
|
|
10432
|
+
});
|
|
10433
|
+
const FindDataSchema = z.strictObject({
|
|
10434
|
+
op: z.literal("find"),
|
|
10435
|
+
hits: z.array(FindHitSchema).max(8),
|
|
10436
|
+
/** True when more documents hold the words than the answer lists. */
|
|
10437
|
+
truncated: z.boolean(),
|
|
10438
|
+
/** True when some document could not be read in full: the answer may miss some. */
|
|
10439
|
+
incomplete: z.boolean().optional()
|
|
10440
|
+
});
|
|
10441
|
+
const KB_CLAIM_MAX_CHARS = 1200;
|
|
10442
|
+
/** The request shapes (without the identifier, the version and the product, which the file reading adds). */
|
|
10443
|
+
const KbListRequestFields = { op: z.literal("kb") };
|
|
10444
|
+
const KbAskRequestFields = {
|
|
10445
|
+
op: z.literal("ask"),
|
|
10446
|
+
/** Chosen by the device: asking again with it reads the same question's state, it never starts a second one. */
|
|
10447
|
+
ask: z.string().regex(ULID_PATTERN),
|
|
10448
|
+
question: z.string().min(1).max(800)
|
|
10449
|
+
};
|
|
10450
|
+
/** One page of the knowledge base. */
|
|
10451
|
+
const KbPageSchema = z.strictObject({
|
|
10452
|
+
/** The path from the product's folder, with `/`. */
|
|
10453
|
+
path: z.string().min(1).max(300),
|
|
10454
|
+
title: z.string().min(1).max(200),
|
|
10455
|
+
/** The first lines of the page, as written. */
|
|
10456
|
+
summary: z.string().max(300).optional(),
|
|
10457
|
+
/** The day the page says it was last updated (`2026-10-01`), when it says one. */
|
|
10458
|
+
updated: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
10459
|
+
/** The last story the page says it took in (`2-3`), when it says one. */
|
|
10460
|
+
story: z.string().regex(/^[1-9]\d{0,5}-[1-9]\d{0,5}$/).optional()
|
|
10461
|
+
});
|
|
10462
|
+
const KbListDataSchema = z.strictObject({
|
|
10463
|
+
op: z.literal("kb"),
|
|
10464
|
+
pages: z.array(KbPageSchema).max(200),
|
|
10465
|
+
/** True when the product holds more pages than the bridge lists. */
|
|
10466
|
+
truncated: z.boolean(),
|
|
10467
|
+
/** True when some page could not be read in full: the list may miss some. */
|
|
10468
|
+
incomplete: z.boolean().optional(),
|
|
10469
|
+
/** Whether the machine can answer a question: an AI tool it knows how to run is there. */
|
|
10470
|
+
askable: z.boolean()
|
|
10471
|
+
});
|
|
10472
|
+
/** A place of the base a statement stands on: the page, the section, the line and the words. */
|
|
10473
|
+
const KbCitationSchema = z.strictObject({
|
|
10474
|
+
path: z.string().min(1).max(300),
|
|
10475
|
+
/** The heading of the section the passage is in, when it has one. */
|
|
10476
|
+
heading: z.string().max(200).optional(),
|
|
10477
|
+
/** The line of the page where the quotation starts, from 1. */
|
|
10478
|
+
line: z.int().min(1).max(1e7),
|
|
10479
|
+
/** The words of the page, as they are written there. */
|
|
10480
|
+
quote: z.string().min(1).max(300)
|
|
10481
|
+
});
|
|
10482
|
+
const KbClaimSchema = z.strictObject({
|
|
10483
|
+
text: z.string().min(1).max(KB_CLAIM_MAX_CHARS),
|
|
10484
|
+
/** At least one: a statement without a source that holds never leaves the bridge. */
|
|
10485
|
+
citations: z.array(KbCitationSchema).min(1).max(4)
|
|
10486
|
+
});
|
|
10487
|
+
const KbCostSchema = z.strictObject({
|
|
10488
|
+
usd: z.number().min(0).max(1e6).optional(),
|
|
10489
|
+
basis: z.string().min(1).max(16),
|
|
10490
|
+
tokens_in: z.int().min(0).max(1e9).optional(),
|
|
10491
|
+
tokens_out: z.int().min(0).max(1e9).optional()
|
|
10492
|
+
});
|
|
10493
|
+
const KbAskDataSchema = z.strictObject({
|
|
10494
|
+
op: z.literal("ask"),
|
|
10495
|
+
ask: z.string().regex(ULID_PATTERN),
|
|
10496
|
+
/** `working`: ask again; `answered`: the answer is below; `failed`: `failure` says why. */
|
|
10497
|
+
state: z.enum([
|
|
10498
|
+
"working",
|
|
10499
|
+
"answered",
|
|
10500
|
+
"failed"
|
|
10501
|
+
]),
|
|
10502
|
+
failure: z.string().min(1).max(32).optional(),
|
|
10503
|
+
answer: z.strictObject({
|
|
10504
|
+
claims: z.array(KbClaimSchema).max(12),
|
|
10505
|
+
/** True when nothing in the base answers the question (or nothing that was said held). */
|
|
10506
|
+
no_source: z.boolean(),
|
|
10507
|
+
/** How many statements the bridge removed because no citation of theirs held. */
|
|
10508
|
+
dropped: z.int().min(0).max(1e3)
|
|
10509
|
+
}).optional(),
|
|
10510
|
+
/** How many passages of the base the AI tool was given (0: it was not asked). */
|
|
10511
|
+
passages: z.int().min(0).max(100),
|
|
10512
|
+
cost: KbCostSchema.optional()
|
|
10513
|
+
});
|
|
10514
|
+
/** The local reference holding the last journal commit projected: never pushed. */
|
|
10515
|
+
const JOURNAL_PROJECTED_REF = "refs/agentplugged/projected";
|
|
10516
|
+
const IgnoredEventSchema = z.strictObject({
|
|
10517
|
+
id: z.string(),
|
|
10518
|
+
reason: z.enum([
|
|
10519
|
+
"unknown_signer",
|
|
10520
|
+
"bad_signature",
|
|
10521
|
+
"broken_chain",
|
|
10522
|
+
"wrong_product",
|
|
10523
|
+
"invalid",
|
|
10524
|
+
"key_conflict",
|
|
10525
|
+
"product_conflict",
|
|
10526
|
+
"test_type",
|
|
10527
|
+
"checkpoint_id_collision",
|
|
10528
|
+
"not_human",
|
|
10529
|
+
"already_resolved",
|
|
10530
|
+
"unendorsed_human",
|
|
10531
|
+
"checkpoint_unknown",
|
|
10532
|
+
"decrypt_failed",
|
|
10533
|
+
"key_proof_invalid",
|
|
10534
|
+
"sensitive_in_clear",
|
|
10535
|
+
"unexpected_encryption",
|
|
10536
|
+
"unsupported_alg",
|
|
10537
|
+
"untrusted_authorization",
|
|
10538
|
+
"device_revoked",
|
|
10539
|
+
"dismissed",
|
|
10540
|
+
"not_reopenable"
|
|
10541
|
+
])
|
|
10542
|
+
});
|
|
10543
|
+
/** Two valid events of one device name the same `prev` (two clones on one machine): both kept. */
|
|
10544
|
+
const ChainForkWarningSchema = z.strictObject({
|
|
10545
|
+
code: z.literal("chain_fork"),
|
|
10546
|
+
device: z.string(),
|
|
10547
|
+
prev: z.string().nullable(),
|
|
10548
|
+
ids: z.array(z.string())
|
|
10549
|
+
});
|
|
10550
|
+
/**
|
|
10551
|
+
* Two `checkpoint.opened` events give the same checkpoint id: `ids[0]` opened it, the others are
|
|
10552
|
+
* ignored (`checkpoint_id_collision`).
|
|
10553
|
+
*/
|
|
10554
|
+
const CheckpointIdCollisionWarningSchema = z.strictObject({
|
|
10555
|
+
code: z.literal("checkpoint_id_collision"),
|
|
10556
|
+
checkpoint: z.string(),
|
|
10557
|
+
ids: z.array(z.string())
|
|
10558
|
+
});
|
|
10559
|
+
/**
|
|
10560
|
+
* A signed `checkpoint.opened` asks a weaker recipient or confirmation than the rules give: the
|
|
10561
|
+
* projection applies the rules' values, and says so. `fields` names what was raised.
|
|
10562
|
+
*/
|
|
10563
|
+
const CheckpointWeakPayloadWarningSchema = z.strictObject({
|
|
10564
|
+
code: z.literal("checkpoint_weak_payload"),
|
|
10565
|
+
checkpoint: z.string(),
|
|
10566
|
+
id: z.string(),
|
|
10567
|
+
fields: z.array(z.enum(["recipient_role", "confirmation"]))
|
|
10568
|
+
});
|
|
10569
|
+
/**
|
|
10570
|
+
* A `high` risk checkpoint answered with the `locale` assurance only (no `presence` credential):
|
|
10571
|
+
* accepted, and shown as such.
|
|
10572
|
+
*/
|
|
10573
|
+
const LocaleOnHighRiskWarningSchema = z.strictObject({
|
|
10574
|
+
code: z.literal("locale_on_high_risk"),
|
|
10575
|
+
checkpoint: z.string(),
|
|
10576
|
+
id: z.string()
|
|
10577
|
+
});
|
|
10578
|
+
z.strictObject({
|
|
10579
|
+
code: z.literal("sensitive_unreadable"),
|
|
10580
|
+
count: z.number().int().min(1)
|
|
10581
|
+
});
|
|
10582
|
+
/**
|
|
10583
|
+
* Warnings drawn from the journal alone, the same on every machine. Machine-local ones (a history
|
|
10584
|
+
* rewritten since this machine's last projection, sensitive content this machine cannot read)
|
|
10585
|
+
* are never written to `state.json`.
|
|
10586
|
+
*/
|
|
10587
|
+
const ProjectionWarningSchema = z.discriminatedUnion("code", [
|
|
10588
|
+
ChainForkWarningSchema,
|
|
10589
|
+
CheckpointIdCollisionWarningSchema,
|
|
10590
|
+
CheckpointWeakPayloadWarningSchema,
|
|
10591
|
+
LocaleOnHighRiskWarningSchema
|
|
10592
|
+
]);
|
|
10593
|
+
const ProjectedDeviceSchema = z.strictObject({
|
|
10594
|
+
device: z.string(),
|
|
10595
|
+
public_key: z.string()
|
|
10596
|
+
});
|
|
10597
|
+
/**
|
|
10598
|
+
* `state.json`: deterministic (two machines write the same bytes from the same journal, in
|
|
10599
|
+
* canonical JSON), `computed_at` being the date (`at`) of the last event applied in projection
|
|
10600
|
+
* order, never the clock.
|
|
10601
|
+
*/
|
|
10602
|
+
const ProjectionStateSchema = z.strictObject({
|
|
10603
|
+
format: z.literal(1),
|
|
10604
|
+
/** The journal position projected (a commit id in Git), or null for an empty journal. */
|
|
10605
|
+
journal_head: z.string().nullable(),
|
|
10606
|
+
/** The `at` of the last event applied, in projection order; null when none was applied. */
|
|
10607
|
+
computed_at: z.string().nullable(),
|
|
10608
|
+
/** The product id of the journal's `product.created`, or null. */
|
|
10609
|
+
product: z.string().nullable(),
|
|
10610
|
+
/** The registered machines, sorted by `device` (the creators are in `state.humans`). */
|
|
10611
|
+
devices: z.array(ProjectedDeviceSchema),
|
|
10612
|
+
/** Events applied. */
|
|
10613
|
+
applied: z.number().int().min(0),
|
|
10614
|
+
/** Events ignored, sorted by `id` then `reason`. */
|
|
10615
|
+
ignored: z.array(IgnoredEventSchema),
|
|
10616
|
+
warnings: z.array(ProjectionWarningSchema),
|
|
10617
|
+
/** What the reducers built. */
|
|
10618
|
+
state: z.record(z.string(), z.unknown())
|
|
10619
|
+
});
|
|
10620
|
+
//#endregion
|
|
10621
|
+
//#region ../../packages/protocol/dist/published-state.js
|
|
10622
|
+
/**
|
|
10623
|
+
* Where a view stands in a journal: `n` is the number of events the journal held (it never goes
|
|
10624
|
+
* down) and `heads` the head of each writer's chain.
|
|
10625
|
+
*/
|
|
10626
|
+
const FeedCursorSchema = z.strictObject({
|
|
10627
|
+
n: z.number().int().min(0),
|
|
10628
|
+
heads: z.record(z.string().min(1).max(64), z.string().min(1).max(128))
|
|
10629
|
+
});
|
|
10630
|
+
/** The devices the journal trusts, as the views need them. */
|
|
10631
|
+
const PublishedTrustedDeviceSchema = z.strictObject({
|
|
10632
|
+
kind: z.enum([
|
|
10633
|
+
"machine",
|
|
10634
|
+
"human",
|
|
10635
|
+
"phone",
|
|
10636
|
+
"browser"
|
|
10637
|
+
]),
|
|
10638
|
+
sig_key: z.string().min(1).max(128),
|
|
10639
|
+
enc_key: z.string().min(1).max(128).nullable(),
|
|
10640
|
+
provisional: z.boolean()
|
|
10641
|
+
});
|
|
10642
|
+
/**
|
|
10643
|
+
* Where the import of an earlier history stands: the events brought in out of those the journal
|
|
10644
|
+
* holds, and the totals of the product (the creator reads « 48 stories, 131 validations »).
|
|
10645
|
+
* `ignored` counts the events the journal holds that were refused as forged, unsigned or cut from
|
|
10646
|
+
* their chain: they are told, never counted among the validations.
|
|
10647
|
+
*/
|
|
10648
|
+
const ImportInfoSchema = z.strictObject({
|
|
10649
|
+
state: z.enum([
|
|
10650
|
+
"none",
|
|
10651
|
+
"running",
|
|
10652
|
+
"done",
|
|
10653
|
+
"failed"
|
|
10654
|
+
]),
|
|
10655
|
+
progress: z.strictObject({
|
|
10656
|
+
events_done: z.number().int().nonnegative(),
|
|
10657
|
+
events_total: z.number().int().nonnegative()
|
|
10658
|
+
}).optional(),
|
|
10659
|
+
stories: z.number().int().nonnegative().optional(),
|
|
10660
|
+
validations: z.number().int().nonnegative().optional(),
|
|
10661
|
+
ignored: z.number().int().nonnegative().optional(),
|
|
10662
|
+
/** Why the import stopped (a short code, never a received value); with `failed` only. */
|
|
10663
|
+
reason: z.string().max(100).optional()
|
|
10664
|
+
});
|
|
10665
|
+
/**
|
|
10666
|
+
* What the exit guard puts in place of a text it kept from leaving. A stable code, not a sentence:
|
|
10667
|
+
* a view recognises it and shows it in the creator's language.
|
|
10668
|
+
*/
|
|
10669
|
+
const REDACTED_SECRET = "redacted:secret";
|
|
10670
|
+
/**
|
|
10671
|
+
* What the main bridge publishes of a product. The projection is the one `get_state` returns; the
|
|
10672
|
+
* trust registry is the journal's (never the relay's list); the machines say which bridge is
|
|
10673
|
+
* main; the import says where the import of an earlier history stands.
|
|
10674
|
+
*/
|
|
10675
|
+
const PublishedStateSchema = z.strictObject({
|
|
10676
|
+
projection: ProjectionStateSchema,
|
|
10677
|
+
trust: z.strictObject({
|
|
10678
|
+
/** True while the journal's root differs from the pinned one: no order is accepted. */
|
|
10679
|
+
blocked: z.boolean(),
|
|
10680
|
+
devices: z.record(z.string().regex(DEVICE_ID_PATTERN), PublishedTrustedDeviceSchema),
|
|
10681
|
+
revoked: z.array(z.string().regex(DEVICE_ID_PATTERN))
|
|
10682
|
+
}),
|
|
10683
|
+
machines: z.strictObject({
|
|
10684
|
+
primary: z.string().regex(BRIDGE_ID_PATTERN).nullable(),
|
|
10685
|
+
/** True when nobody chose the main bridge: the engine elected it from the journal. */
|
|
10686
|
+
automatic: z.boolean()
|
|
10687
|
+
}),
|
|
10688
|
+
import: ImportInfoSchema
|
|
10689
|
+
});
|
|
10690
|
+
readTolerant(z.strictObject({
|
|
10691
|
+
current: z.enum(PHASE_IDS).nullable(),
|
|
10692
|
+
since: z.string().nullable(),
|
|
10693
|
+
phases: z.array(z.strictObject({
|
|
10694
|
+
id: z.enum(PHASE_IDS),
|
|
10695
|
+
status: z.enum([
|
|
10696
|
+
"done",
|
|
10697
|
+
"in_progress",
|
|
10698
|
+
"in_review",
|
|
10699
|
+
"upcoming",
|
|
10700
|
+
"skipped"
|
|
10701
|
+
])
|
|
10702
|
+
})).max(PHASE_IDS.length)
|
|
10703
|
+
}));
|
|
10704
|
+
/**
|
|
10705
|
+
* What a story's file says, as the main bridge read it from the product's working copy (the
|
|
10706
|
+
* `story_docs` key of `projection.state` of the published state, set only for the files the
|
|
10707
|
+
* bridge could read and that follow the story contract): the title, the owner, the requirements
|
|
10708
|
+
* it covers, the stories it depends on, its branch and budget, its intent and its acceptance
|
|
10709
|
+
* criteria in order. The state's `state` record is open, so a view that does not know this key
|
|
10710
|
+
* still reads the rest; a view that does reads each entry with this schema and leaves out the
|
|
10711
|
+
* ones that do not fit.
|
|
10712
|
+
*/
|
|
10713
|
+
const PublishedStoryDocSchema = z.strictObject({
|
|
10714
|
+
title: z.string().min(1).max(400),
|
|
10715
|
+
owner: z.string().min(1).max(64).optional(),
|
|
10716
|
+
epic: z.string().min(1).max(64).optional(),
|
|
10717
|
+
requirements: z.array(z.string().min(1).max(64)).max(40),
|
|
10718
|
+
depends_on: z.array(z.string().min(1).max(64)).max(40),
|
|
10719
|
+
branch: z.string().min(1).max(400).optional(),
|
|
10720
|
+
budget_usd: z.number().finite().nonnegative().optional(),
|
|
10721
|
+
intent: z.string().max(1200),
|
|
10722
|
+
criteria: z.array(z.string().max(400)).max(40)
|
|
10723
|
+
});
|
|
10724
|
+
readTolerant(PublishedStoryDocSchema);
|
|
10725
|
+
z.record(z.string().regex(/^[1-9]\d{0,5}-[1-9]\d{0,5}$/), PublishedStoryDocSchema);
|
|
10726
|
+
const PATH_SEGMENT_MAX = 200;
|
|
10727
|
+
const PATH_DEPTH_MAX = 16;
|
|
10728
|
+
const FEED_OPS_MAX = 2e4;
|
|
10729
|
+
/** One step of a change: set the value at a path, or remove it. */
|
|
10730
|
+
const FeedOpSchema = z.discriminatedUnion("op", [z.strictObject({
|
|
10731
|
+
op: z.literal("set"),
|
|
10732
|
+
path: z.array(z.string().max(PATH_SEGMENT_MAX)).min(1).max(PATH_DEPTH_MAX),
|
|
10733
|
+
value: z.unknown()
|
|
10734
|
+
}), z.strictObject({
|
|
10735
|
+
op: z.literal("remove"),
|
|
10736
|
+
path: z.array(z.string().max(PATH_SEGMENT_MAX)).min(1).max(PATH_DEPTH_MAX)
|
|
10737
|
+
})]);
|
|
10738
|
+
const FeedSnapshotSchema = z.strictObject({
|
|
10739
|
+
kind: z.literal("snapshot"),
|
|
10740
|
+
schema: z.number().int().positive(),
|
|
10741
|
+
product: z.string().regex(ULID_PATTERN),
|
|
10742
|
+
cursor: FeedCursorSchema,
|
|
10743
|
+
state: PublishedStateSchema
|
|
10744
|
+
});
|
|
10745
|
+
const FeedChangeSchema = z.strictObject({
|
|
10746
|
+
kind: z.literal("change"),
|
|
10747
|
+
schema: z.number().int().positive(),
|
|
10748
|
+
product: z.string().regex(ULID_PATTERN),
|
|
10749
|
+
from_cursor: FeedCursorSchema,
|
|
10750
|
+
to_cursor: FeedCursorSchema,
|
|
10751
|
+
ops: z.array(FeedOpSchema).max(FEED_OPS_MAX)
|
|
10752
|
+
});
|
|
10753
|
+
z.strictObject({
|
|
10754
|
+
/** The same for all the parts of one message. */
|
|
10755
|
+
id: z.string().regex(ULID_PATTERN),
|
|
10756
|
+
index: z.number().int().min(0).max(4095),
|
|
10757
|
+
count: z.number().int().min(1).max(4096),
|
|
10758
|
+
/** SHA-256 of the whole content, base64url (43 characters). */
|
|
10759
|
+
digest: z.string().length(43).refine(isCanonicalBase64url),
|
|
10760
|
+
data: z.string().min(1).max(262144).refine(isCanonicalBase64url)
|
|
10761
|
+
});
|
|
10762
|
+
/** The bridge's presence: signed (as the message that carries it) and dated. */
|
|
10763
|
+
const FeedBeatSchema = z.strictObject({
|
|
10764
|
+
bridge: z.string().regex(BRIDGE_ID_PATTERN),
|
|
10765
|
+
/** Seconds since the epoch, on the bridge's clock. */
|
|
10766
|
+
at: z.number().int().positive(),
|
|
10767
|
+
bridge_version: SemverSchema,
|
|
10768
|
+
protocol_version: z.number().int().positive(),
|
|
10769
|
+
schema: z.number().int().positive(),
|
|
10770
|
+
/** The last sequence number this bridge gave to a message for this view. */
|
|
10771
|
+
last_seq: z.number().int().min(0),
|
|
10772
|
+
/** The products this bridge publishes, and the cursor of each. */
|
|
10773
|
+
publishes: z.array(z.strictObject({
|
|
10774
|
+
product: z.string().regex(ULID_PATTERN),
|
|
10775
|
+
cursor: FeedCursorSchema
|
|
10776
|
+
})).max(64),
|
|
10777
|
+
report: MachineReportSchema.optional()
|
|
10778
|
+
});
|
|
10779
|
+
z.strictObject({
|
|
10780
|
+
schema: z.number().int().positive(),
|
|
10781
|
+
product: z.string().regex(ULID_PATTERN),
|
|
10782
|
+
cursor: FeedCursorSchema.nullable()
|
|
10783
|
+
});
|
|
10784
|
+
z.strictObject({
|
|
10785
|
+
product: z.string().regex(ULID_PATTERN),
|
|
10786
|
+
cursor: FeedCursorSchema
|
|
10787
|
+
});
|
|
10788
|
+
const FeedUpgradeRequiredSchema = z.strictObject({
|
|
10789
|
+
current: z.number().int().positive(),
|
|
10790
|
+
minimum: z.number().int().positive()
|
|
10791
|
+
});
|
|
10792
|
+
/**
|
|
10793
|
+
* What a bridge tells a device it just removed: the device it means, the product whose journal
|
|
10794
|
+
* holds the removal and the date. The device believes it only from the bridge it pinned when it was
|
|
10795
|
+
* linked (the message is signed by that bridge's key), never from the relay closing a connection.
|
|
10796
|
+
*/
|
|
10797
|
+
const FeedDeviceRemovedSchema = z.strictObject({
|
|
10798
|
+
device: z.string().regex(DEVICE_ID_PATTERN),
|
|
10799
|
+
product: z.string().regex(ULID_PATTERN),
|
|
10800
|
+
removed_at: z.iso.datetime()
|
|
10801
|
+
});
|
|
10802
|
+
readTolerant(PublishedStateSchema);
|
|
10803
|
+
readTolerant(FeedSnapshotSchema);
|
|
10804
|
+
readTolerant(FeedChangeSchema);
|
|
10805
|
+
readTolerant(FeedBeatSchema);
|
|
10806
|
+
readTolerant(FeedUpgradeRequiredSchema);
|
|
10807
|
+
readTolerant(FeedDeviceRemovedSchema);
|
|
10808
|
+
/** The most bytes one `vault_put` or `vault_blob` frame carries (decoded). */
|
|
10809
|
+
const VAULT_CHUNK_BYTES = 196608;
|
|
10810
|
+
/** What the relay keeps apart: a full vault, or the changes after it. */
|
|
10811
|
+
const VAULT_KINDS = ["full", "delta"];
|
|
10812
|
+
const b64 = (bytes) => z.string().length(Math.ceil(bytes * 4 / 3)).refine(isCanonicalBase64url);
|
|
10813
|
+
/** The content key sealed for one device: an ephemeral X25519 key, a nonce and the sealed key. */
|
|
10814
|
+
const VaultKeyBoxSchema = z.strictObject({
|
|
10815
|
+
device: z.string().regex(DEVICE_ID_PATTERN),
|
|
10816
|
+
/** The sender's ephemeral X25519 public key (32 bytes). */
|
|
10817
|
+
eph: b64(32),
|
|
10818
|
+
/** XChaCha20-Poly1305 nonce (24 bytes). */
|
|
10819
|
+
nonce: b64(24),
|
|
10820
|
+
/** The content key (32 bytes) and the authentication tag (16 bytes). */
|
|
10821
|
+
box: b64(48)
|
|
10822
|
+
});
|
|
10823
|
+
z.strictObject({
|
|
10824
|
+
v: z.literal(1),
|
|
10825
|
+
product: z.string().regex(ULID_PATTERN),
|
|
10826
|
+
kind: z.enum(VAULT_KINDS),
|
|
10827
|
+
rev: z.number().int().positive(),
|
|
10828
|
+
base_rev: z.number().int().positive().nullable(),
|
|
10829
|
+
/** When the machine sealed the blob, ISO 8601. */
|
|
10830
|
+
at: z.iso.datetime(),
|
|
10831
|
+
/** The machine that sealed it, by its journal identifier. */
|
|
10832
|
+
bridge: z.string().regex(BRIDGE_ID_PATTERN),
|
|
10833
|
+
boxes: z.array(VaultKeyBoxSchema).max(64),
|
|
10834
|
+
/** XChaCha20-Poly1305 nonce of the content (24 bytes). */
|
|
10835
|
+
nonce: b64(24),
|
|
10836
|
+
/** The compressed, encrypted content. */
|
|
10837
|
+
body: z.string().min(1).refine(isCanonicalBase64url),
|
|
10838
|
+
/** Signature of the header and of the digest of `body`. */
|
|
10839
|
+
sig: z.string().length(86).refine(isCanonicalBase64url)
|
|
10840
|
+
});
|
|
10841
|
+
readTolerant(z.strictObject({
|
|
10842
|
+
content: z.discriminatedUnion("kind", [FeedSnapshotSchema, FeedChangeSchema]),
|
|
10843
|
+
report: MachineReportSchema.optional()
|
|
10844
|
+
}));
|
|
8484
10845
|
//#endregion
|
|
8485
10846
|
//#region ../../packages/protocol/dist/relay.js
|
|
8486
10847
|
/**
|
|
@@ -8520,7 +10881,8 @@ const KEY_32 = z.string().length(43).refine(isCanonicalBase64url);
|
|
|
8520
10881
|
*/
|
|
8521
10882
|
const RelayAttachDeclarationSchema = z.strictObject({
|
|
8522
10883
|
device_id: RelayIdSchema,
|
|
8523
|
-
|
|
10884
|
+
/** `bridge` for a machine attached during a rendezvous, `browser` for a browser registering itself. */
|
|
10885
|
+
kind: z.enum(["bridge", "browser"]),
|
|
8524
10886
|
sig_key: KEY_32,
|
|
8525
10887
|
enc_key: KEY_32,
|
|
8526
10888
|
relay: z.string().min(1).max(253),
|
|
@@ -8607,15 +10969,84 @@ z.discriminatedUnion("type", [
|
|
|
8607
10969
|
/** Opaque bytes in base64url. */
|
|
8608
10970
|
data: z.string().min(1).max(base64urlLength(RENDEZVOUS_MAX_DATA_BYTES)).refine(isCanonicalBase64url)
|
|
8609
10971
|
}),
|
|
10972
|
+
z.strictObject({
|
|
10973
|
+
type: z.literal("device_register"),
|
|
10974
|
+
declaration: RelayAttachDeclarationSchema,
|
|
10975
|
+
signature: SignatureSchema,
|
|
10976
|
+
credential: z.string().min(1).max(4096).optional()
|
|
10977
|
+
}),
|
|
10978
|
+
z.strictObject({
|
|
10979
|
+
type: z.literal("device_revoke"),
|
|
10980
|
+
sig_key: KEY_32.optional(),
|
|
10981
|
+
/** Instead of the key: the relay's own identifier of the device (for a device no journal knows). */
|
|
10982
|
+
device_id: RelayIdSchema.optional()
|
|
10983
|
+
}).refine((frame) => frame.sig_key === void 0 !== (frame.device_id === void 0), { message: "name the device by its key or by its identifier, not both" }),
|
|
10984
|
+
z.strictObject({
|
|
10985
|
+
type: z.literal("vault_put"),
|
|
10986
|
+
product: RelayIdSchema,
|
|
10987
|
+
kind: z.enum(VAULT_KINDS),
|
|
10988
|
+
/** Rises with every blob of the product. */
|
|
10989
|
+
rev: z.number().int().positive(),
|
|
10990
|
+
/** The revision a change follows; null for a full vault. */
|
|
10991
|
+
base_rev: z.number().int().positive().nullable(),
|
|
10992
|
+
index: z.number().int().min(0).max(255),
|
|
10993
|
+
count: z.number().int().min(1).max(256),
|
|
10994
|
+
data: z.string().min(1).max(base64urlLength(VAULT_CHUNK_BYTES)).refine(isCanonicalBase64url)
|
|
10995
|
+
}),
|
|
10996
|
+
z.strictObject({
|
|
10997
|
+
type: z.literal("vault_get"),
|
|
10998
|
+
product: RelayIdSchema
|
|
10999
|
+
}),
|
|
11000
|
+
z.strictObject({ type: z.literal("outbox_list") }),
|
|
11001
|
+
z.strictObject({
|
|
11002
|
+
type: z.literal("push_register"),
|
|
11003
|
+
platform: z.enum(PUSH_PLATFORMS),
|
|
11004
|
+
token: PushTokenSchema
|
|
11005
|
+
}),
|
|
11006
|
+
z.strictObject({ type: z.literal("push_unregister") }),
|
|
11007
|
+
z.strictObject({
|
|
11008
|
+
type: z.literal("notify"),
|
|
11009
|
+
...PushNoticeSchema.shape
|
|
11010
|
+
}),
|
|
11011
|
+
z.strictObject({
|
|
11012
|
+
type: z.literal("notify_clear"),
|
|
11013
|
+
id: PushNoticeSchema.shape.id
|
|
11014
|
+
}),
|
|
11015
|
+
z.strictObject({
|
|
11016
|
+
type: z.literal("presence_report"),
|
|
11017
|
+
surface: z.enum(NOTIFY_SURFACES),
|
|
11018
|
+
active: z.boolean()
|
|
11019
|
+
}),
|
|
11020
|
+
z.strictObject({ type: z.literal("notify_prefs_get") }),
|
|
11021
|
+
z.strictObject({ type: z.literal("modules_get") }),
|
|
11022
|
+
z.strictObject({
|
|
11023
|
+
type: z.literal("notify_prefs_set"),
|
|
11024
|
+
prefs: NotifyPrefsSchema
|
|
11025
|
+
}),
|
|
11026
|
+
z.strictObject({
|
|
11027
|
+
type: z.literal("outbox_cancel"),
|
|
11028
|
+
id: RelayIdSchema
|
|
11029
|
+
}),
|
|
8610
11030
|
z.strictObject({
|
|
8611
11031
|
type: z.literal("rv_attach"),
|
|
8612
11032
|
declaration: RelayAttachDeclarationSchema,
|
|
8613
|
-
signature: SignatureSchema
|
|
11033
|
+
signature: SignatureSchema,
|
|
11034
|
+
/**
|
|
11035
|
+
* A bridge already attached to the account that vouches for ANOTHER machine's declaration
|
|
11036
|
+
* (the machine signed it itself): the identifier of the voucher on this relay.
|
|
11037
|
+
*/
|
|
11038
|
+
voucher: RelayIdSchema.optional()
|
|
8614
11039
|
})
|
|
8615
11040
|
]);
|
|
8616
11041
|
const PresenceEntrySchema = z.strictObject({
|
|
8617
11042
|
device_id: RelayIdSchema,
|
|
8618
11043
|
kind: z.enum(RELAY_DEVICE_KINDS),
|
|
11044
|
+
/**
|
|
11045
|
+
* The public signing key of the device: a device of the account's journal is recognised by it (the
|
|
11046
|
+
* relay's identifiers mean nothing to a journal), and a device no journal knows by its key is shown
|
|
11047
|
+
* as not verified.
|
|
11048
|
+
*/
|
|
11049
|
+
sig_key: KEY_32.optional(),
|
|
8619
11050
|
online: z.boolean(),
|
|
8620
11051
|
/** ISO 8601, `null` when the device never connected. */
|
|
8621
11052
|
last_seen_at: z.string().nullable()
|
|
@@ -8671,33 +11102,104 @@ z.discriminatedUnion("type", [
|
|
|
8671
11102
|
x: RendezvousIdSchema
|
|
8672
11103
|
}),
|
|
8673
11104
|
z.strictObject({
|
|
8674
|
-
type: z.literal("rv_joined"),
|
|
8675
|
-
x: RendezvousIdSchema,
|
|
8676
|
-
peer: z.string().min(1).max(32),
|
|
8677
|
-
open: z.boolean()
|
|
11105
|
+
type: z.literal("rv_joined"),
|
|
11106
|
+
x: RendezvousIdSchema,
|
|
11107
|
+
peer: z.string().min(1).max(32),
|
|
11108
|
+
open: z.boolean()
|
|
11109
|
+
}),
|
|
11110
|
+
z.strictObject({
|
|
11111
|
+
type: z.literal("rv_peer"),
|
|
11112
|
+
peer: z.string().min(1).max(32)
|
|
11113
|
+
}),
|
|
11114
|
+
z.strictObject({
|
|
11115
|
+
type: z.literal("rv_message"),
|
|
11116
|
+
id: RelayIdSchema,
|
|
11117
|
+
peer: z.string().min(1).max(32).optional(),
|
|
11118
|
+
data: z.string().min(1).max(base64urlLength(RENDEZVOUS_MAX_DATA_BYTES)).refine(isCanonicalBase64url)
|
|
11119
|
+
}),
|
|
11120
|
+
z.strictObject({
|
|
11121
|
+
type: z.literal("rv_sent"),
|
|
11122
|
+
id: RelayIdSchema,
|
|
11123
|
+
delivered: z.boolean()
|
|
11124
|
+
}),
|
|
11125
|
+
z.strictObject({
|
|
11126
|
+
type: z.literal("rv_attached"),
|
|
11127
|
+
device_id: RelayIdSchema
|
|
11128
|
+
}),
|
|
11129
|
+
z.strictObject({
|
|
11130
|
+
type: z.literal("device_registered"),
|
|
11131
|
+
device_id: RelayIdSchema
|
|
11132
|
+
}),
|
|
11133
|
+
z.strictObject({
|
|
11134
|
+
type: z.literal("device_revoked"),
|
|
11135
|
+
found: z.boolean(),
|
|
11136
|
+
/** The relay's identifier of the device it stopped serving; null when there was none. */
|
|
11137
|
+
device_id: RelayIdSchema.nullable()
|
|
11138
|
+
}),
|
|
11139
|
+
z.strictObject({
|
|
11140
|
+
type: z.literal("device_gone"),
|
|
11141
|
+
device_id: RelayIdSchema
|
|
11142
|
+
}),
|
|
11143
|
+
z.strictObject({
|
|
11144
|
+
type: z.literal("vault_stored"),
|
|
11145
|
+
product: RelayIdSchema,
|
|
11146
|
+
rev: z.number().int().positive()
|
|
11147
|
+
}),
|
|
11148
|
+
z.strictObject({
|
|
11149
|
+
type: z.literal("vault_blob"),
|
|
11150
|
+
product: RelayIdSchema,
|
|
11151
|
+
rev: z.number().int().positive(),
|
|
11152
|
+
base_rev: z.number().int().positive().nullable(),
|
|
11153
|
+
kind: z.enum(VAULT_KINDS),
|
|
11154
|
+
/** When the relay received it, ISO 8601. */
|
|
11155
|
+
stored_at: z.iso.datetime(),
|
|
11156
|
+
index: z.number().int().min(0).max(255),
|
|
11157
|
+
count: z.number().int().min(1).max(256),
|
|
11158
|
+
data: z.string().min(1).max(base64urlLength(VAULT_CHUNK_BYTES)).refine(isCanonicalBase64url)
|
|
11159
|
+
}),
|
|
11160
|
+
z.strictObject({
|
|
11161
|
+
type: z.literal("vault_end"),
|
|
11162
|
+
product: RelayIdSchema,
|
|
11163
|
+
blobs: z.number().int().min(0)
|
|
11164
|
+
}),
|
|
11165
|
+
z.strictObject({ type: z.literal("vault_cleared") }),
|
|
11166
|
+
z.strictObject({ type: z.literal("push_registered") }),
|
|
11167
|
+
z.strictObject({ type: z.literal("push_unregistered") }),
|
|
11168
|
+
z.strictObject({
|
|
11169
|
+
type: z.literal("notified"),
|
|
11170
|
+
id: PushNoticeSchema.shape.id,
|
|
11171
|
+
sent: z.number().int().min(0).max(1e3)
|
|
11172
|
+
}),
|
|
11173
|
+
z.strictObject({
|
|
11174
|
+
type: z.literal("modules"),
|
|
11175
|
+
modules: z.array(z.string().regex(EXTENSION_MODULE_PATTERN)).max(32)
|
|
8678
11176
|
}),
|
|
8679
11177
|
z.strictObject({
|
|
8680
|
-
type: z.literal("
|
|
8681
|
-
|
|
11178
|
+
type: z.literal("notify_prefs"),
|
|
11179
|
+
prefs: NotifyPrefsSchema
|
|
8682
11180
|
}),
|
|
8683
11181
|
z.strictObject({
|
|
8684
|
-
type: z.literal("
|
|
8685
|
-
|
|
8686
|
-
peer: z.string().min(1).max(32).optional(),
|
|
8687
|
-
data: z.string().min(1).max(base64urlLength(RENDEZVOUS_MAX_DATA_BYTES)).refine(isCanonicalBase64url)
|
|
11182
|
+
type: z.literal("notice"),
|
|
11183
|
+
...PushNoticeSchema.shape
|
|
8688
11184
|
}),
|
|
8689
11185
|
z.strictObject({
|
|
8690
|
-
type: z.literal("
|
|
8691
|
-
|
|
8692
|
-
|
|
11186
|
+
type: z.literal("outbox"),
|
|
11187
|
+
items: z.array(z.strictObject({
|
|
11188
|
+
id: RelayIdSchema,
|
|
11189
|
+
to: RelayIdSchema,
|
|
11190
|
+
queued_at: z.iso.datetime(),
|
|
11191
|
+
expires_at: z.iso.datetime()
|
|
11192
|
+
})).max(500)
|
|
8693
11193
|
}),
|
|
8694
11194
|
z.strictObject({
|
|
8695
|
-
type: z.literal("
|
|
8696
|
-
|
|
11195
|
+
type: z.literal("outbox_cancelled"),
|
|
11196
|
+
id: RelayIdSchema,
|
|
11197
|
+
cancelled: z.boolean()
|
|
8697
11198
|
})
|
|
8698
11199
|
]);
|
|
8699
11200
|
//#endregion
|
|
8700
11201
|
//#region ../../packages/protocol/dist/pairing.js
|
|
11202
|
+
const PRODUCT_ID = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/;
|
|
8701
11203
|
/**
|
|
8702
11204
|
* The messages of the pairing link. Generic on purpose
|
|
8703
11205
|
*: schemas and codes only; the link's bytes and the boxes are in `packages/crypto`, the
|
|
@@ -8709,6 +11211,10 @@ z.discriminatedUnion("type", [
|
|
|
8709
11211
|
* - `envelope` (bridge to browser): a message from peer to peer encrypted for the
|
|
8710
11212
|
* browser's key and signed by the bridge: `link.welcome` then one `product.keys` per product;
|
|
8711
11213
|
* - `refused` (bridge to browser): a code and nothing else; informative, not authenticated.
|
|
11214
|
+
*
|
|
11215
|
+
* A machine that joins with a short code does not hold a link's secret: both sides run a password
|
|
11216
|
+
* exchange first (`pake_*` messages), and the box key is drawn from the session key it gives. The
|
|
11217
|
+
* bridge then says who it is in `hello`; the machine answers with its `request`.
|
|
8712
11218
|
*/
|
|
8713
11219
|
/** Codes of the pairing, said by the bridge (separate from `ErrorCode`, like the relay's). */
|
|
8714
11220
|
const PAIR_ERROR_CODES = {
|
|
@@ -8716,22 +11222,50 @@ const PAIR_ERROR_CODES = {
|
|
|
8716
11222
|
PAIRING_EXPIRED: "PAIRING_EXPIRED",
|
|
8717
11223
|
PAIRING_INVALID_PROOF: "PAIRING_INVALID_PROOF",
|
|
8718
11224
|
PAIRING_KEY_MISMATCH: "PAIRING_KEY_MISMATCH",
|
|
11225
|
+
/** A machine joined from a folder whose product the bridge that gave the code does not serve. */
|
|
11226
|
+
PAIRING_PRODUCT_UNKNOWN: "PAIRING_PRODUCT_UNKNOWN",
|
|
8719
11227
|
PAIRING_WRONG_ACCOUNT: "PAIRING_WRONG_ACCOUNT"
|
|
8720
11228
|
};
|
|
8721
11229
|
/** The type labels of the messages (`t` of the signed ones, `type` of the boxes and messages). */
|
|
8722
11230
|
const PAIR_MESSAGE_TYPES = {
|
|
8723
11231
|
request: "link.request/v1",
|
|
8724
11232
|
welcome: TRUST_SIGNATURE_TAGS.linkWelcome,
|
|
8725
|
-
productKeys: "product.keys/v1"
|
|
11233
|
+
productKeys: "product.keys/v1",
|
|
11234
|
+
hello: "pair.hello/v1"
|
|
8726
11235
|
};
|
|
8727
11236
|
const key32 = z.string().length(43).refine(isCanonicalBase64url);
|
|
11237
|
+
const key22 = z.string().length(22).refine(isCanonicalBase64url);
|
|
8728
11238
|
const label = z.string().min(1).max(40);
|
|
11239
|
+
/** The devices that answer a pairing: a browser, a phone or another machine. */
|
|
11240
|
+
const REQUESTER_DEVICE_ID_PATTERN = /^(?:browser|phone|machine):[A-Za-z0-9_-]{16}$/;
|
|
11241
|
+
const PairCodeIdSchema = z.string().regex(/^[0-9A-HJKMNP-TV-Z]{8}$/);
|
|
11242
|
+
const PairCodeSecretSchema = z.string().regex(/^[0-9A-HJKMNP-TV-Z]{6}$/);
|
|
11243
|
+
/** `pair.offer/v1` for a link or a QR: a linked device asks its bridge to open a rendezvous. */
|
|
11244
|
+
const PairLinkOfferOrderSchema = z.strictObject({
|
|
11245
|
+
t: z.literal(TRUST_SIGNATURE_TAGS.pairOffer),
|
|
11246
|
+
/** Which kind of device the link is for: another browser or a phone. */
|
|
11247
|
+
kind: z.enum(["link", "phone"]),
|
|
11248
|
+
/** The rendezvous identifier: 16 random bytes in base64url. */
|
|
11249
|
+
x: z.string().length(22).refine(isCanonicalBase64url),
|
|
11250
|
+
/** The link's secret: 16 random bytes in base64url. Travels inside the encrypted order only. */
|
|
11251
|
+
s: z.string().length(22).refine(isCanonicalBase64url)
|
|
11252
|
+
});
|
|
11253
|
+
/** `pair.offer/v1` for another machine: the short code, drawn by the linked device. */
|
|
11254
|
+
const PairCodeOfferOrderSchema = z.strictObject({
|
|
11255
|
+
t: z.literal(TRUST_SIGNATURE_TAGS.pairOffer),
|
|
11256
|
+
kind: z.literal("machine"),
|
|
11257
|
+
/** The address to meet at (8 symbols). */
|
|
11258
|
+
x: PairCodeIdSchema,
|
|
11259
|
+
/** The password (6 symbols). Travels inside the encrypted order only. */
|
|
11260
|
+
w: PairCodeSecretSchema
|
|
11261
|
+
});
|
|
11262
|
+
z.union([PairLinkOfferOrderSchema, PairCodeOfferOrderSchema]);
|
|
8729
11263
|
z.strictObject({
|
|
8730
11264
|
t: z.literal(PAIR_MESSAGE_TYPES.request),
|
|
8731
11265
|
/** The account of the requester, as the relay knows it. */
|
|
8732
11266
|
account: RelayIdSchema,
|
|
8733
11267
|
browser: z.strictObject({
|
|
8734
|
-
device: z.string().regex(
|
|
11268
|
+
device: z.string().regex(REQUESTER_DEVICE_ID_PATTERN),
|
|
8735
11269
|
sig: DeviceSigKeySchema,
|
|
8736
11270
|
enc: DeviceEncKeySchema
|
|
8737
11271
|
}),
|
|
@@ -8739,7 +11273,29 @@ z.strictObject({
|
|
|
8739
11273
|
declared: z.strictObject({
|
|
8740
11274
|
type: label,
|
|
8741
11275
|
os: label
|
|
8742
|
-
})
|
|
11276
|
+
}),
|
|
11277
|
+
/**
|
|
11278
|
+
* The requester's signature recognising the creator's key as the root of the link (the root
|
|
11279
|
+
* the link carried): it ties that key to the account without any gesture. Optional: a requester
|
|
11280
|
+
* that does not sign it is linked all the same, only the key stays unattached.
|
|
11281
|
+
*/
|
|
11282
|
+
root_certification: z.strictObject({
|
|
11283
|
+
issued_at: z.iso.datetime(),
|
|
11284
|
+
sig: Ed25519SignatureSchema
|
|
11285
|
+
}).optional(),
|
|
11286
|
+
/**
|
|
11287
|
+
* Only a machine joining with a short code: the product it serves and the declaration it
|
|
11288
|
+
* signed to be attached to the relay (the bridge that holds the rendezvous vouches for it).
|
|
11289
|
+
*/
|
|
11290
|
+
machine: z.strictObject({
|
|
11291
|
+
product: z.string().regex(PRODUCT_ID),
|
|
11292
|
+
relay_device: RelayIdSchema,
|
|
11293
|
+
attach_expires_at: z.number().int().positive(),
|
|
11294
|
+
attach_sig: Ed25519SignatureSchema
|
|
11295
|
+
}).optional()
|
|
11296
|
+
}).refine((request) => request.browser.device.startsWith("machine:") === (request.machine !== void 0), {
|
|
11297
|
+
path: ["machine"],
|
|
11298
|
+
message: "a machine says what it serves, and only a machine does"
|
|
8743
11299
|
});
|
|
8744
11300
|
z.strictObject({
|
|
8745
11301
|
t: z.literal(PAIR_MESSAGE_TYPES.welcome),
|
|
@@ -8753,9 +11309,11 @@ z.strictObject({
|
|
|
8753
11309
|
enc: z.strictObject({
|
|
8754
11310
|
alg: z.literal(DEVICE_ENC_ALG),
|
|
8755
11311
|
key: key32
|
|
8756
|
-
})
|
|
11312
|
+
}),
|
|
11313
|
+
/** The identifier the relay knows the bridge by: where the browser's messages to it go. */
|
|
11314
|
+
relay_device: z.string().regex(/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/).optional()
|
|
8757
11315
|
}),
|
|
8758
|
-
browser: z.string().regex(
|
|
11316
|
+
browser: z.string().regex(REQUESTER_DEVICE_ID_PATTERN),
|
|
8759
11317
|
/** SHA-256 of the opened request, base64url. */
|
|
8760
11318
|
request: key32,
|
|
8761
11319
|
registry: z.array(z.strictObject({
|
|
@@ -8769,12 +11327,38 @@ z.strictObject({
|
|
|
8769
11327
|
})).max(64),
|
|
8770
11328
|
sig: Ed25519SignatureSchema
|
|
8771
11329
|
});
|
|
11330
|
+
z.strictObject({
|
|
11331
|
+
t: z.literal(PAIR_MESSAGE_TYPES.hello),
|
|
11332
|
+
root: z.strictObject({
|
|
11333
|
+
device: z.string().regex(/^(machine|human):[A-Za-z0-9_-]{16}$/),
|
|
11334
|
+
key: key32
|
|
11335
|
+
}),
|
|
11336
|
+
bridge: z.strictObject({
|
|
11337
|
+
device: z.string().regex(/^machine:[A-Za-z0-9_-]{16}$/),
|
|
11338
|
+
sig: DeviceSigKeySchema,
|
|
11339
|
+
enc: z.strictObject({
|
|
11340
|
+
alg: z.literal(DEVICE_ENC_ALG),
|
|
11341
|
+
key: key32
|
|
11342
|
+
})
|
|
11343
|
+
}),
|
|
11344
|
+
/** The account of the bridge, which the machine joins. */
|
|
11345
|
+
account: RelayIdSchema,
|
|
11346
|
+
now: z.number().int().positive()
|
|
11347
|
+
});
|
|
8772
11348
|
z.strictObject({
|
|
8773
11349
|
t: z.literal(PAIR_MESSAGE_TYPES.productKeys),
|
|
8774
|
-
product: z.string().regex(
|
|
11350
|
+
product: z.string().regex(PRODUCT_ID),
|
|
8775
11351
|
/** The identifier of the key (`apkeyid1`): the browser checks it against the journal's events. */
|
|
8776
11352
|
key_id: z.string().min(1).max(64),
|
|
8777
|
-
key: key32
|
|
11353
|
+
key: key32,
|
|
11354
|
+
/**
|
|
11355
|
+
* The keys the product had before it was renewed, so that a device linked afterwards reads the
|
|
11356
|
+
* whole history. Each is checked against its own `key_id` by the receiver.
|
|
11357
|
+
*/
|
|
11358
|
+
older: z.array(z.strictObject({
|
|
11359
|
+
key_id: z.string().min(1).max(64),
|
|
11360
|
+
key: key32
|
|
11361
|
+
})).max(32).optional()
|
|
8778
11362
|
});
|
|
8779
11363
|
z.discriminatedUnion("k", [
|
|
8780
11364
|
z.strictObject({
|
|
@@ -8793,111 +11377,526 @@ z.discriminatedUnion("k", [
|
|
|
8793
11377
|
ciphertext: z.string().min(1).max(65536)
|
|
8794
11378
|
})
|
|
8795
11379
|
}),
|
|
11380
|
+
z.strictObject({
|
|
11381
|
+
k: z.literal("pake_init"),
|
|
11382
|
+
nonce: key22,
|
|
11383
|
+
share: key32
|
|
11384
|
+
}),
|
|
11385
|
+
z.strictObject({
|
|
11386
|
+
k: z.literal("pake_reply"),
|
|
11387
|
+
share: key32
|
|
11388
|
+
}),
|
|
11389
|
+
z.strictObject({
|
|
11390
|
+
k: z.literal("pake_confirm"),
|
|
11391
|
+
mac: key32
|
|
11392
|
+
}),
|
|
11393
|
+
z.strictObject({
|
|
11394
|
+
k: z.literal("pake_confirm_b"),
|
|
11395
|
+
mac: key32
|
|
11396
|
+
}),
|
|
11397
|
+
z.strictObject({
|
|
11398
|
+
k: z.literal("hello"),
|
|
11399
|
+
nonce: z.string().min(1).max(64),
|
|
11400
|
+
ciphertext: z.string().min(1).max(65536)
|
|
11401
|
+
}),
|
|
8796
11402
|
z.strictObject({
|
|
8797
11403
|
k: z.literal("refused"),
|
|
8798
11404
|
code: z.enum(Object.values(PAIR_ERROR_CODES))
|
|
8799
11405
|
})
|
|
8800
11406
|
]);
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
8816
|
-
|
|
8817
|
-
|
|
8818
|
-
|
|
8819
|
-
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
11407
|
+
//#endregion
|
|
11408
|
+
//#region ../../packages/protocol/dist/product-browse.js
|
|
11409
|
+
/**
|
|
11410
|
+
* Choosing where a product is made, by clicking: the shapes the request and the answer of the file
|
|
11411
|
+
* reading carry (`folders`). A device names a place the machine listed in its report and the steps
|
|
11412
|
+
* taken inside it, one folder name each, as the machine listed them; it never sends a path. The
|
|
11413
|
+
* machine resolves the steps itself, refuses whatever leaves the place (a link, a junction, a
|
|
11414
|
+
* separator, `..`), and lists only the folders a creator may put a product in: no hidden folder,
|
|
11415
|
+
* no folder of the system.
|
|
11416
|
+
*/
|
|
11417
|
+
/** The request shape (without the identifier, the version and the product, which the file reading adds). */
|
|
11418
|
+
const FoldersRequestFields = {
|
|
11419
|
+
op: z.literal("folders"),
|
|
11420
|
+
/** The identifier of a place the machine listed. */
|
|
11421
|
+
location: z.string().regex(PRODUCT_LOCATION_ID_PATTERN),
|
|
11422
|
+
/** The folders gone into, from the place. */
|
|
11423
|
+
path: z.array(z.string().refine(isBrowseStep, { message: "a folder name, not a path" })).max(12).optional()
|
|
11424
|
+
};
|
|
11425
|
+
const FoldersDataSchema = z.strictObject({
|
|
11426
|
+
op: z.literal("folders"),
|
|
11427
|
+
location: z.string().regex(PRODUCT_LOCATION_ID_PATTERN),
|
|
11428
|
+
/** The steps the answer is about (the request's). */
|
|
11429
|
+
path: z.array(z.string().refine(isBrowseStep)).max(12),
|
|
11430
|
+
/** The folder as the creator reads it (the home folder written `~`). */
|
|
11431
|
+
label: z.string().min(1).max(1200),
|
|
11432
|
+
/** The folders inside it that can be gone into, by name. */
|
|
11433
|
+
folders: z.array(z.string().refine(isBrowseStep)).max(200),
|
|
11434
|
+
/** True when there are more folders than listed. */
|
|
11435
|
+
truncated: z.boolean()
|
|
8829
11436
|
});
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
11437
|
+
//#endregion
|
|
11438
|
+
//#region ../../packages/protocol/dist/voice-read.js
|
|
11439
|
+
/** The largest piece of audio of one request, in bytes of PCM (3 seconds); the envelope must hold it sealed. */
|
|
11440
|
+
const TRANSCRIBE_CHUNK_MAX_BYTES = 96e3;
|
|
11441
|
+
/** The longest text the machine gives back, in characters. */
|
|
11442
|
+
const TRANSCRIBE_TEXT_MAX_CHARS = 4e3;
|
|
11443
|
+
/** What the request carries on top of the file reading's own fields. */
|
|
11444
|
+
const TranscribeRequestFields = {
|
|
11445
|
+
op: z.literal("transcribe"),
|
|
11446
|
+
/**
|
|
11447
|
+
* `probe`: asks whether the machine can transcribe, with no audio. `audio`: one piece of a
|
|
11448
|
+
* phrase; the last one (`final`) makes the machine transcribe and answer with the text.
|
|
11449
|
+
*/
|
|
11450
|
+
step: z.enum(["probe", "audio"]),
|
|
11451
|
+
/** The phrase the piece belongs to (audio only). */
|
|
11452
|
+
session: z.string().regex(ULID_PATTERN).optional(),
|
|
11453
|
+
/** The place of the piece in the phrase, from 0 (audio only). */
|
|
11454
|
+
seq: z.int().min(0).max(7).optional(),
|
|
11455
|
+
/** True on the last piece of the phrase (audio only). */
|
|
11456
|
+
final: z.boolean().optional(),
|
|
11457
|
+
/** The language to recognize, a BCP-47 tag (audio only). */
|
|
11458
|
+
language: z.string().regex(/^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})?$/).optional(),
|
|
11459
|
+
/** The PCM of the piece, base64url (audio only). */
|
|
11460
|
+
audio: z.string().regex(/^[A-Za-z0-9_-]*$/).max(base64urlLength(TRANSCRIBE_CHUNK_MAX_BYTES)).optional()
|
|
11461
|
+
};
|
|
11462
|
+
const TranscribeDataSchema = z.strictObject({
|
|
11463
|
+
op: z.literal("transcribe"),
|
|
11464
|
+
/** True when the machine has a transcription command set up. */
|
|
11465
|
+
ready: z.boolean(),
|
|
11466
|
+
/** The words understood: only on the answer to the last piece of a phrase. */
|
|
11467
|
+
text: z.string().max(TRANSCRIBE_TEXT_MAX_CHARS).optional()
|
|
8836
11468
|
});
|
|
11469
|
+
/** The longest path, in characters, and the longest one name of a branch. */
|
|
11470
|
+
const FILES_PATH_MAX = 1024;
|
|
8837
11471
|
/**
|
|
8838
|
-
*
|
|
8839
|
-
*
|
|
11472
|
+
* Where a file is read: the working copy of the product as it is now (uncommitted work included),
|
|
11473
|
+
* a branch or a commit of its repository, or the working copy of the story that works on a branch.
|
|
8840
11474
|
*/
|
|
8841
|
-
const
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
11475
|
+
const FilesRefSchema = z.discriminatedUnion("kind", [
|
|
11476
|
+
z.strictObject({ kind: z.literal("worktree") }),
|
|
11477
|
+
z.strictObject({
|
|
11478
|
+
kind: z.literal("branch"),
|
|
11479
|
+
name: z.string().min(1).max(200)
|
|
11480
|
+
}),
|
|
11481
|
+
z.strictObject({
|
|
11482
|
+
kind: z.literal("commit"),
|
|
11483
|
+
sha: z.string().regex(/^[0-9a-f]{40,64}$/)
|
|
11484
|
+
}),
|
|
11485
|
+
z.strictObject({
|
|
11486
|
+
kind: z.literal("workspace"),
|
|
11487
|
+
branch: z.string().min(1).max(200)
|
|
11488
|
+
})
|
|
11489
|
+
]);
|
|
11490
|
+
const RequestBase = {
|
|
11491
|
+
v: z.literal(1),
|
|
11492
|
+
/** Chosen by the asking device: the answer repeats it. */
|
|
11493
|
+
id: z.string().regex(ULID_PATTERN),
|
|
11494
|
+
product: z.string().regex(ULID_PATTERN)
|
|
11495
|
+
};
|
|
11496
|
+
z.discriminatedUnion("op", [
|
|
11497
|
+
z.strictObject({
|
|
11498
|
+
...RequestBase,
|
|
11499
|
+
op: z.literal("tree"),
|
|
11500
|
+
ref: FilesRefSchema,
|
|
11501
|
+
path: z.string().max(FILES_PATH_MAX)
|
|
11502
|
+
}),
|
|
11503
|
+
z.strictObject({
|
|
11504
|
+
...RequestBase,
|
|
11505
|
+
op: z.literal("file"),
|
|
11506
|
+
ref: FilesRefSchema,
|
|
11507
|
+
path: z.string().min(1).max(FILES_PATH_MAX)
|
|
11508
|
+
}),
|
|
11509
|
+
z.strictObject({
|
|
11510
|
+
...RequestBase,
|
|
11511
|
+
op: z.literal("download"),
|
|
11512
|
+
ref: FilesRefSchema,
|
|
11513
|
+
path: z.string().min(1).max(FILES_PATH_MAX)
|
|
11514
|
+
}),
|
|
11515
|
+
z.strictObject({
|
|
11516
|
+
...RequestBase,
|
|
11517
|
+
op: z.literal("refs")
|
|
11518
|
+
}),
|
|
11519
|
+
z.strictObject({
|
|
11520
|
+
...RequestBase,
|
|
11521
|
+
op: z.literal("changes"),
|
|
11522
|
+
base: FilesRefSchema,
|
|
11523
|
+
head: FilesRefSchema
|
|
11524
|
+
}),
|
|
11525
|
+
z.strictObject({
|
|
11526
|
+
...RequestBase,
|
|
11527
|
+
op: z.literal("live"),
|
|
11528
|
+
after: z.number().int().min(0).optional(),
|
|
11529
|
+
epoch: z.string().min(1).max(64).optional()
|
|
11530
|
+
}),
|
|
11531
|
+
z.strictObject({
|
|
11532
|
+
...RequestBase,
|
|
11533
|
+
...CodePullsRequestFields
|
|
11534
|
+
}),
|
|
11535
|
+
z.strictObject({
|
|
11536
|
+
...RequestBase,
|
|
11537
|
+
...CodePullRequestFields
|
|
11538
|
+
}),
|
|
11539
|
+
z.strictObject({
|
|
11540
|
+
...RequestBase,
|
|
11541
|
+
...CodeDiffRequestFields
|
|
11542
|
+
}),
|
|
11543
|
+
z.strictObject({
|
|
11544
|
+
...RequestBase,
|
|
11545
|
+
...CodeEditorRequestFields
|
|
11546
|
+
}),
|
|
11547
|
+
z.strictObject({
|
|
11548
|
+
...RequestBase,
|
|
11549
|
+
...DocsListRequestFields
|
|
11550
|
+
}),
|
|
11551
|
+
z.strictObject({
|
|
11552
|
+
...RequestBase,
|
|
11553
|
+
...DocsTraceRequestFields
|
|
11554
|
+
}),
|
|
11555
|
+
z.strictObject({
|
|
11556
|
+
...RequestBase,
|
|
11557
|
+
...DocsRetrosRequestFields
|
|
11558
|
+
}),
|
|
11559
|
+
z.strictObject({
|
|
11560
|
+
...RequestBase,
|
|
11561
|
+
...KbListRequestFields
|
|
11562
|
+
}),
|
|
11563
|
+
z.strictObject({
|
|
11564
|
+
...RequestBase,
|
|
11565
|
+
...KbAskRequestFields
|
|
11566
|
+
}),
|
|
11567
|
+
z.strictObject({
|
|
11568
|
+
...RequestBase,
|
|
11569
|
+
...CostsRequestFields
|
|
11570
|
+
}),
|
|
11571
|
+
z.strictObject({
|
|
11572
|
+
...RequestBase,
|
|
11573
|
+
...BriefingRequestFields
|
|
11574
|
+
}),
|
|
11575
|
+
z.strictObject({
|
|
11576
|
+
...RequestBase,
|
|
11577
|
+
...TranscribeRequestFields
|
|
11578
|
+
}),
|
|
11579
|
+
z.strictObject({
|
|
11580
|
+
...RequestBase,
|
|
11581
|
+
...FindRequestFields
|
|
11582
|
+
}),
|
|
11583
|
+
z.strictObject({
|
|
11584
|
+
...RequestBase,
|
|
11585
|
+
...FoldersRequestFields
|
|
11586
|
+
})
|
|
11587
|
+
]);
|
|
11588
|
+
/** Why a request was not answered with data; stable codes the views translate. */
|
|
11589
|
+
const FilesErrorCodeSchema = z.enum([
|
|
11590
|
+
"NOT_FOUND",
|
|
11591
|
+
"OUTSIDE_PRODUCT",
|
|
11592
|
+
"PROTECTED",
|
|
11593
|
+
"TOO_LARGE",
|
|
11594
|
+
"WRONG_KIND",
|
|
11595
|
+
"BAD_REQUEST",
|
|
11596
|
+
"UNAVAILABLE",
|
|
11597
|
+
"BUSY",
|
|
11598
|
+
...CODE_ERROR_CODES
|
|
11599
|
+
]);
|
|
11600
|
+
const FilesEntrySchema = z.strictObject({
|
|
11601
|
+
name: z.string().min(1).max(255),
|
|
11602
|
+
kind: z.enum([
|
|
11603
|
+
"file",
|
|
11604
|
+
"dir",
|
|
11605
|
+
"link"
|
|
11606
|
+
]),
|
|
11607
|
+
/** Bytes; absent for a folder. */
|
|
11608
|
+
size: z.number().int().min(0).optional(),
|
|
11609
|
+
/** True for a file the bridge never sends (secrets): the name is visible, the content is not. */
|
|
11610
|
+
protected: z.literal(true).optional()
|
|
11611
|
+
});
|
|
11612
|
+
const FilesBranchSchema = z.strictObject({
|
|
11613
|
+
name: z.string().min(1).max(200),
|
|
11614
|
+
sha: z.string().regex(/^[0-9a-f]{40,64}$/),
|
|
11615
|
+
/** True for the branch the product's folder is on now. */
|
|
11616
|
+
current: z.literal(true).optional(),
|
|
11617
|
+
/** True when a working copy of this branch exists on the machine (a story at work). */
|
|
11618
|
+
workspace: z.literal(true).optional()
|
|
11619
|
+
});
|
|
11620
|
+
const FilesChangeSchema = z.strictObject({
|
|
11621
|
+
status: z.enum([
|
|
11622
|
+
"added",
|
|
11623
|
+
"modified",
|
|
11624
|
+
"deleted",
|
|
11625
|
+
"renamed"
|
|
11626
|
+
]),
|
|
11627
|
+
path: z.string().min(1).max(FILES_PATH_MAX),
|
|
11628
|
+
/** The old path of a renamed file. */
|
|
11629
|
+
from: z.string().min(1).max(FILES_PATH_MAX).optional(),
|
|
11630
|
+
protected: z.literal(true).optional()
|
|
8845
11631
|
});
|
|
8846
11632
|
/**
|
|
8847
|
-
*
|
|
8848
|
-
*
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
|
|
11633
|
+
* One line of what an agent does. `kind` is a stable code; `text` is plain text the view never
|
|
11634
|
+
* interprets: what the agent said (`say`), the name of a tool it called (`tool`, `tool_ok`,
|
|
11635
|
+
* `tool_failed`, `denied`) or the outcome code of the run (`end`).
|
|
11636
|
+
*
|
|
11637
|
+
* Three kinds tell what a call does to the machine: `read` and `edit` carry the path of the file,
|
|
11638
|
+
* relative to the agent's working folder, and `command` carries the command; a command's issue
|
|
11639
|
+
* (`command_ok`, `command_failed`) has no text and points to its command by `of`. A path that
|
|
11640
|
+
* leaves the working folder is never told: the line has `outside` and no text. `protected` marks
|
|
11641
|
+
* a file whose content the bridge never shows. `link` says where to read what an `edit` changed:
|
|
11642
|
+
* the same references and paths as the file reading (`file`, `changes`). The results of a tool,
|
|
11643
|
+
* and the content of files, are never in a line.
|
|
11644
|
+
*
|
|
11645
|
+
* In a conversation with Noah one more kind appears: `creator`, the creator's own message (plain
|
|
11646
|
+
* text, so that their other devices see it). What Noah says is a `say` of the agent `noah`:
|
|
11647
|
+
* untrusted text, like any agent's.
|
|
11648
|
+
*/
|
|
11649
|
+
const LiveLineSchema = z.strictObject({
|
|
11650
|
+
seq: z.number().int().min(1),
|
|
11651
|
+
/** Milliseconds since the epoch, on the bridge's clock. */
|
|
11652
|
+
at: z.number().int().min(0),
|
|
11653
|
+
agent: z.string().min(1).max(64),
|
|
11654
|
+
kind: z.enum([
|
|
11655
|
+
"start",
|
|
11656
|
+
"say",
|
|
11657
|
+
"tool",
|
|
11658
|
+
"tool_ok",
|
|
11659
|
+
"tool_failed",
|
|
11660
|
+
"denied",
|
|
11661
|
+
"end",
|
|
11662
|
+
"read",
|
|
11663
|
+
"edit",
|
|
11664
|
+
"command",
|
|
11665
|
+
"command_ok",
|
|
11666
|
+
"command_failed",
|
|
11667
|
+
"creator"
|
|
11668
|
+
]),
|
|
11669
|
+
text: z.string().max(2e3).optional(),
|
|
11670
|
+
/** For a command's issue: the `seq` of the line of the command. */
|
|
11671
|
+
of: z.number().int().min(1).optional(),
|
|
11672
|
+
/** The path leaves the agent's working folder: it is not told. */
|
|
11673
|
+
outside: z.literal(true).optional(),
|
|
11674
|
+
/** The file is a secret's: its name is told, its content is never shown. */
|
|
11675
|
+
protected: z.literal(true).optional(),
|
|
11676
|
+
/** The line comes from a session the creator opened in the tool's own terminal. */
|
|
11677
|
+
terminal: z.literal(true).optional(),
|
|
11678
|
+
/** For a change: where its content and its difference can be read. */
|
|
11679
|
+
link: z.strictObject({
|
|
11680
|
+
ref: FilesRefSchema,
|
|
11681
|
+
path: z.string().min(1).max(FILES_PATH_MAX)
|
|
11682
|
+
}).optional()
|
|
8855
11683
|
});
|
|
8856
11684
|
/**
|
|
8857
|
-
*
|
|
8858
|
-
*
|
|
11685
|
+
* Whether the sessions the creator opens in a tool's own terminal can be followed: `listening`
|
|
11686
|
+
* (the tool's hook is in the repository), `hook_missing` (it is not: `sync` writes it),
|
|
11687
|
+
* `unsupported` (the tool has no such hook yet). A stable code the views tell in the creator's language.
|
|
8859
11688
|
*/
|
|
8860
|
-
const
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
|
|
8866
|
-
|
|
8867
|
-
|
|
8868
|
-
|
|
11689
|
+
const LiveTerminalStateSchema = z.enum([
|
|
11690
|
+
"listening",
|
|
11691
|
+
"hook_missing",
|
|
11692
|
+
"unsupported"
|
|
11693
|
+
]);
|
|
11694
|
+
/** Where the team of the product stands, as a code the views tell in the creator's language. */
|
|
11695
|
+
const LiveChainStateSchema = z.enum([
|
|
11696
|
+
"none",
|
|
11697
|
+
"running",
|
|
11698
|
+
"waiting",
|
|
11699
|
+
"paused",
|
|
11700
|
+
"stopped"
|
|
11701
|
+
]);
|
|
11702
|
+
/** What an answer carries, by operation. */
|
|
11703
|
+
const FilesDataSchema = z.discriminatedUnion("op", [
|
|
11704
|
+
CodePullsDataSchema,
|
|
11705
|
+
CodePullDataSchema,
|
|
11706
|
+
CodeDiffDataSchema,
|
|
11707
|
+
CodeEditorDataSchema,
|
|
11708
|
+
DocsListDataSchema,
|
|
11709
|
+
DocsTraceDataSchema,
|
|
11710
|
+
DocsRetrosDataSchema,
|
|
11711
|
+
KbListDataSchema,
|
|
11712
|
+
KbAskDataSchema,
|
|
11713
|
+
FindDataSchema,
|
|
11714
|
+
CostsDataSchema,
|
|
11715
|
+
BriefingDataSchema,
|
|
11716
|
+
TranscribeDataSchema,
|
|
11717
|
+
FoldersDataSchema,
|
|
11718
|
+
z.strictObject({
|
|
11719
|
+
op: z.literal("tree"),
|
|
11720
|
+
path: z.string().max(FILES_PATH_MAX),
|
|
11721
|
+
entries: z.array(FilesEntrySchema).max(5e3),
|
|
11722
|
+
/** True when the folder holds more than the bridge lists. */
|
|
11723
|
+
truncated: z.boolean()
|
|
11724
|
+
}),
|
|
11725
|
+
z.strictObject({
|
|
11726
|
+
op: z.literal("file"),
|
|
11727
|
+
path: z.string().min(1).max(FILES_PATH_MAX),
|
|
11728
|
+
size: z.number().int().min(0),
|
|
11729
|
+
/** `text`: shown; `binary`: only offered for download. */
|
|
11730
|
+
content: z.enum(["text", "binary"]),
|
|
11731
|
+
/** True when the file can be asked for as bytes to save (it is not larger than the bridge sends). */
|
|
11732
|
+
downloadable: z.boolean(),
|
|
11733
|
+
/** The text, with each line that looked like a secret replaced by `redacted:secret`. */
|
|
11734
|
+
text: z.string().optional(),
|
|
11735
|
+
/** How many lines were replaced. */
|
|
11736
|
+
masked: z.number().int().min(0)
|
|
11737
|
+
}),
|
|
11738
|
+
z.strictObject({
|
|
11739
|
+
op: z.literal("download"),
|
|
11740
|
+
path: z.string().min(1).max(FILES_PATH_MAX),
|
|
11741
|
+
size: z.number().int().min(0),
|
|
11742
|
+
/** The bytes, base64url. */
|
|
11743
|
+
bytes: z.string()
|
|
11744
|
+
}),
|
|
11745
|
+
z.strictObject({
|
|
11746
|
+
op: z.literal("refs"),
|
|
11747
|
+
branches: z.array(FilesBranchSchema).max(500),
|
|
11748
|
+
truncated: z.boolean()
|
|
11749
|
+
}),
|
|
11750
|
+
z.strictObject({
|
|
11751
|
+
op: z.literal("changes"),
|
|
11752
|
+
files: z.array(FilesChangeSchema).max(5e3),
|
|
11753
|
+
truncated: z.boolean()
|
|
11754
|
+
}),
|
|
11755
|
+
z.strictObject({
|
|
11756
|
+
op: z.literal("live"),
|
|
11757
|
+
/** Tells one run of the bridge's memory from another: send it back with `next`. */
|
|
11758
|
+
epoch: z.string().min(1).max(64),
|
|
11759
|
+
/** True when the cursor asked with was not this bridge's: the lines start again from the oldest kept. */
|
|
11760
|
+
reset: z.boolean(),
|
|
11761
|
+
/** How many lines were lost between the cursor and the first line below (memory is bounded). */
|
|
11762
|
+
dropped: z.number().int().min(0),
|
|
11763
|
+
/** The cursor to ask with next time. */
|
|
11764
|
+
next: z.number().int().min(0),
|
|
11765
|
+
/** True when more lines wait: ask again at once. */
|
|
11766
|
+
more: z.boolean(),
|
|
11767
|
+
chain: LiveChainStateSchema,
|
|
11768
|
+
/**
|
|
11769
|
+
* Present while the creator holds the whole team: `paused` (the agents finish their step and
|
|
11770
|
+
* start nothing) or `stopped` (the emergency stop cut what the bridge started).
|
|
11771
|
+
*/
|
|
11772
|
+
team: z.enum(["paused", "stopped"]).optional(),
|
|
11773
|
+
/** Present while notices wait for Noah (a validation he has not read yet) and why. */
|
|
11774
|
+
notices: LiveNoticesSchema.optional(),
|
|
11775
|
+
/** The agent that works now, or null. */
|
|
11776
|
+
working: z.strictObject({
|
|
11777
|
+
agent: z.string().min(1).max(64),
|
|
11778
|
+
story: z.string().min(1).max(32).optional(),
|
|
11779
|
+
tool: z.string().min(1).max(32)
|
|
11780
|
+
}).nullable(),
|
|
11781
|
+
lines: z.array(LiveLineSchema).max(100),
|
|
11782
|
+
/** For each tool, whether its sessions opened in the creator's own terminal can be followed. */
|
|
11783
|
+
terminal: z.array(z.strictObject({
|
|
11784
|
+
tool: z.enum([
|
|
11785
|
+
"claude-code",
|
|
11786
|
+
"codex",
|
|
11787
|
+
"antigravity",
|
|
11788
|
+
"cursor"
|
|
11789
|
+
]),
|
|
11790
|
+
state: LiveTerminalStateSchema
|
|
11791
|
+
})).max(8).optional()
|
|
11792
|
+
})
|
|
11793
|
+
]);
|
|
11794
|
+
const FilesResponseSchema = z.discriminatedUnion("ok", [z.strictObject({
|
|
11795
|
+
v: z.literal(1),
|
|
11796
|
+
id: z.string().regex(ULID_PATTERN),
|
|
11797
|
+
product: z.string().regex(ULID_PATTERN),
|
|
11798
|
+
ok: z.literal(true),
|
|
11799
|
+
/** The commit the answer was read at, or `worktree` for the working copy. */
|
|
11800
|
+
at: z.string().max(80),
|
|
11801
|
+
/** Milliseconds since the epoch, on the bridge's clock: when it was read. */
|
|
11802
|
+
read_at: z.number().int().min(0),
|
|
11803
|
+
data: FilesDataSchema
|
|
11804
|
+
}), z.strictObject({
|
|
11805
|
+
v: z.literal(1),
|
|
11806
|
+
id: z.string().regex(ULID_PATTERN),
|
|
11807
|
+
product: z.string().regex(ULID_PATTERN),
|
|
11808
|
+
ok: z.literal(false),
|
|
11809
|
+
code: FilesErrorCodeSchema,
|
|
11810
|
+
/** For the platform codes: the host of the repository, so that the device can name it. */
|
|
11811
|
+
host: ForgeHostSchema.optional(),
|
|
11812
|
+
/** For `TOO_LARGE`: the size of the file and what the bridge shows at most. */
|
|
11813
|
+
size: z.number().int().min(0).optional(),
|
|
11814
|
+
max: z.number().int().min(0).optional()
|
|
11815
|
+
})]);
|
|
11816
|
+
readTolerant(FilesDataSchema);
|
|
11817
|
+
readTolerant(FilesResponseSchema);
|
|
11818
|
+
//#endregion
|
|
11819
|
+
//#region ../../packages/protocol/dist/share.js
|
|
8869
11820
|
/**
|
|
8870
|
-
*
|
|
8871
|
-
*
|
|
8872
|
-
*
|
|
11821
|
+
* The read-only progress page: a page a creator shares without any account. The device builds it
|
|
11822
|
+
* from a short allow-list (never a copy of the state), seals it with a key made for this one link,
|
|
11823
|
+
* and hands the relay the sealed bytes only. The key travels in the fragment of the link, which no
|
|
11824
|
+
* browser sends to a server, so the relay can neither read the page nor give it to anyone who does
|
|
11825
|
+
* not hold the whole link. The relay erases the page at its expiry or when the creator withdraws
|
|
11826
|
+
* the link.
|
|
8873
11827
|
*/
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
11828
|
+
/**
|
|
11829
|
+
* The stages and the phases a page can name. Written here, not imported, so that the page that
|
|
11830
|
+
* reads a link stays light (a test compares them with the lists of the product and of the method).
|
|
11831
|
+
*/
|
|
11832
|
+
const SHARE_STAGES = [
|
|
11833
|
+
"framing",
|
|
11834
|
+
"development",
|
|
11835
|
+
"launch",
|
|
11836
|
+
"online"
|
|
11837
|
+
];
|
|
11838
|
+
const SHARE_PHASES = [
|
|
11839
|
+
"framing",
|
|
11840
|
+
"planning",
|
|
11841
|
+
"design",
|
|
11842
|
+
"build",
|
|
11843
|
+
"delivery",
|
|
11844
|
+
"launch"
|
|
11845
|
+
];
|
|
11846
|
+
/** The largest sealed page, in bytes. */
|
|
11847
|
+
const SHARE_MAX_SEALED_BYTES = 65536;
|
|
11848
|
+
/** The identifier of a link: 16 random bytes, in base64url (22 characters). */
|
|
11849
|
+
const SHARE_ID_PATTERN = /^[A-Za-z0-9_-]{22}$/;
|
|
11850
|
+
const sealedText = (maxBytes) => z.string().min(1).max(Math.ceil(maxBytes * 4 / 3)).refine(isCanonicalBase64url);
|
|
11851
|
+
z.strictObject({
|
|
11852
|
+
product: z.string().regex(/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/),
|
|
11853
|
+
/** 12 bytes. */
|
|
11854
|
+
nonce: z.string().length(16).refine(isCanonicalBase64url),
|
|
11855
|
+
ciphertext: sealedText(SHARE_MAX_SEALED_BYTES)
|
|
8883
11856
|
});
|
|
11857
|
+
z.strictObject({ id: z.string().regex(SHARE_ID_PATTERN) });
|
|
8884
11858
|
z.strictObject({
|
|
8885
|
-
|
|
8886
|
-
/**
|
|
8887
|
-
|
|
8888
|
-
|
|
8889
|
-
|
|
8890
|
-
|
|
8891
|
-
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
|
|
8896
|
-
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
11859
|
+
v: z.literal(1),
|
|
11860
|
+
/** When the page was made, in milliseconds since the epoch. */
|
|
11861
|
+
made_at: z.number().int().nonnegative(),
|
|
11862
|
+
product: z.strictObject({
|
|
11863
|
+
name: z.string().min(1).max(80),
|
|
11864
|
+
stage: z.enum(SHARE_STAGES)
|
|
11865
|
+
}),
|
|
11866
|
+
phases: z.array(z.strictObject({
|
|
11867
|
+
id: z.enum(SHARE_PHASES),
|
|
11868
|
+
state: z.enum([
|
|
11869
|
+
"done",
|
|
11870
|
+
"current",
|
|
11871
|
+
"review",
|
|
11872
|
+
"upcoming"
|
|
11873
|
+
])
|
|
11874
|
+
})).max(SHARE_PHASES.length),
|
|
11875
|
+
sprint: z.strictObject({
|
|
11876
|
+
goal: z.string().min(1).max(160).nullable(),
|
|
11877
|
+
done: z.number().int().nonnegative(),
|
|
11878
|
+
total: z.number().int().nonnegative()
|
|
11879
|
+
}).nullable(),
|
|
11880
|
+
/** Stories merged, all sprints together. */
|
|
11881
|
+
delivered: z.number().int().nonnegative(),
|
|
11882
|
+
stories: z.array(z.strictObject({
|
|
11883
|
+
id: z.string().regex(/^[A-Za-z0-9.-]{1,16}$/),
|
|
11884
|
+
/** `null` when the title was held back or the story has none. */
|
|
11885
|
+
title: z.string().min(1).max(120).nullable(),
|
|
11886
|
+
state: z.enum([
|
|
11887
|
+
"todo",
|
|
11888
|
+
"development",
|
|
11889
|
+
"review",
|
|
11890
|
+
"done"
|
|
11891
|
+
]),
|
|
11892
|
+
/** Steps (acceptance criteria) proven out of those the story lists; `null` when none is listed. */
|
|
11893
|
+
steps: z.strictObject({
|
|
11894
|
+
done: z.number().int().nonnegative(),
|
|
11895
|
+
total: z.number().int().nonnegative()
|
|
11896
|
+
}).nullable()
|
|
11897
|
+
})).max(60),
|
|
11898
|
+
/** Stories of the sprint past the limit, counted and not listed. */
|
|
11899
|
+
more: z.number().int().nonnegative()
|
|
8901
11900
|
});
|
|
8902
11901
|
/** A page of checkpoints, newest first: filtered by status, type or story, before an id. */
|
|
8903
11902
|
const StateCheckpointsInputSchema = z.strictObject({
|
|
@@ -8985,6 +11984,87 @@ const CursorCliConfigSchema = z.looseObject({ permissions: z.looseObject({
|
|
|
8985
11984
|
allow: z.array(z.string()).optional(),
|
|
8986
11985
|
deny: z.array(z.string()).optional()
|
|
8987
11986
|
}).optional() });
|
|
11987
|
+
/** The CSV export's columns: those of `HistoryEntry`, then the reason of an ignored answer. */
|
|
11988
|
+
const HISTORY_CSV_COLUMNS = [...[
|
|
11989
|
+
"at",
|
|
11990
|
+
"author",
|
|
11991
|
+
"author_trust",
|
|
11992
|
+
"author_endorsed_at",
|
|
11993
|
+
"source",
|
|
11994
|
+
"decision",
|
|
11995
|
+
"option",
|
|
11996
|
+
"waiver_until",
|
|
11997
|
+
"assurance",
|
|
11998
|
+
"locale_on_high_risk",
|
|
11999
|
+
"checkpoint",
|
|
12000
|
+
"checkpoint_type",
|
|
12001
|
+
"checkpoint_risk",
|
|
12002
|
+
"checkpoint_title",
|
|
12003
|
+
"story",
|
|
12004
|
+
"event",
|
|
12005
|
+
"checkpoint_phase",
|
|
12006
|
+
"quote",
|
|
12007
|
+
"relayed_by",
|
|
12008
|
+
"channel",
|
|
12009
|
+
"stack",
|
|
12010
|
+
"via"
|
|
12011
|
+
], "ignored_reason"];
|
|
12012
|
+
/** The UTF-8 byte order mark: spreadsheets then read the accents right. */
|
|
12013
|
+
const BOM = String.fromCharCode(65279);
|
|
12014
|
+
/** Starts a spreadsheet formula when a cell begins with it. */
|
|
12015
|
+
const FORMULA_START = /^[=+\-@\t\r]/;
|
|
12016
|
+
/**
|
|
12017
|
+
* One CSV field (RFC 4180): quoted when it holds a comma, a quote or a line break. A text a
|
|
12018
|
+
* spreadsheet would run as a formula (`=`, `+`, `-`, `@`, tab, CR first) is prefixed with `'`.
|
|
12019
|
+
*/
|
|
12020
|
+
function csvField(value) {
|
|
12021
|
+
let text = value === null ? "" : String(value);
|
|
12022
|
+
if (FORMULA_START.test(text)) text = `'${text}`;
|
|
12023
|
+
return /[",\r\n]/.test(text) ? `"${text.replaceAll("\"", "\"\"")}"` : text;
|
|
12024
|
+
}
|
|
12025
|
+
function ignoredRow(item) {
|
|
12026
|
+
return {
|
|
12027
|
+
at: item.at,
|
|
12028
|
+
author: item.author,
|
|
12029
|
+
author_trust: null,
|
|
12030
|
+
author_endorsed_at: null,
|
|
12031
|
+
source: null,
|
|
12032
|
+
decision: item.decision,
|
|
12033
|
+
option: null,
|
|
12034
|
+
waiver_until: null,
|
|
12035
|
+
assurance: null,
|
|
12036
|
+
locale_on_high_risk: null,
|
|
12037
|
+
checkpoint: item.checkpoint,
|
|
12038
|
+
checkpoint_type: null,
|
|
12039
|
+
checkpoint_risk: null,
|
|
12040
|
+
checkpoint_title: null,
|
|
12041
|
+
story: item.story,
|
|
12042
|
+
checkpoint_phase: null,
|
|
12043
|
+
quote: null,
|
|
12044
|
+
relayed_by: null,
|
|
12045
|
+
channel: null,
|
|
12046
|
+
stack: null,
|
|
12047
|
+
via: null,
|
|
12048
|
+
event: item.event,
|
|
12049
|
+
ignored_reason: item.reason
|
|
12050
|
+
};
|
|
12051
|
+
}
|
|
12052
|
+
/**
|
|
12053
|
+
* The CSV export, for spreadsheets (RFC 4180, UTF-8 with a byte order mark so that accents read
|
|
12054
|
+
* right): a header of the English field names, then one record per entry, then (`--all`) one per
|
|
12055
|
+
* ignored answer with its `ignored_reason` (empty for the valid ones). Each line ends with CRLF; a
|
|
12056
|
+
* null is an empty field; formulas are neutralised (`csvField`), so the JSON export is the exact
|
|
12057
|
+
* one.
|
|
12058
|
+
*/
|
|
12059
|
+
function historyCsv(entries, ignored = null) {
|
|
12060
|
+
const rows = [...entries.map((entry) => ({
|
|
12061
|
+
...entry,
|
|
12062
|
+
ignored_reason: null
|
|
12063
|
+
})), ...(ignored ?? []).map(ignoredRow)];
|
|
12064
|
+
const lines = [HISTORY_CSV_COLUMNS.join(",")];
|
|
12065
|
+
for (const row of rows) lines.push(HISTORY_CSV_COLUMNS.map((column) => csvField(row[column])).join(","));
|
|
12066
|
+
return `${BOM}${lines.map((line) => `${line}\r\n`).join("")}`;
|
|
12067
|
+
}
|
|
8988
12068
|
//#endregion
|
|
8989
12069
|
//#region ../../packages/crypto/dist/encoding.js
|
|
8990
12070
|
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
@@ -9790,9 +12870,4 @@ function gitBlobId(bytes, algorithm = "sha1") {
|
|
|
9790
12870
|
return toHex(algorithm === "sha1" ? sha1(object) : sha256$1(object));
|
|
9791
12871
|
}
|
|
9792
12872
|
//#endregion
|
|
9793
|
-
|
|
9794
|
-
/** Relying party of every `presence` credential. */
|
|
9795
|
-
const WEBAUTHN_RP_ID = "agentplugged.local";
|
|
9796
|
-
const WEBAUTHN_ORIGIN = `https://${WEBAUTHN_RP_ID}`;
|
|
9797
|
-
//#endregion
|
|
9798
|
-
export { MAX_SPRINT_CHECKPOINT_SUMMARY as $, InstallSettingsSchema as $a, RequestCreatorActionInputSchema as $i, STORY_DOCUMENT_ID_PATTERN as $n, REVIEW_LENSES as $r, KNOWLEDGE_CODE_FIELD as $t, REQUIREMENT_STATUSES as A, DeferredIdInputSchema as Aa, hasHiddenCharacter as Ai, SOLO_METHODS as An, resolveGitWorkflow as Ao, AgentOverrideSchema as Ar, HYPOTHESIS_CONFIDENCES as At, RoomIdSchema as B, ReportProgressInputSchema as Ba, ReadMemoryInputSchema as Bi, courseIdOf as Bn, ErrorCode as Bo, qaProofKind as Br, journalRemoteRef as Bt, USAGE_EVENT_TYPES as C, contentSize as Ca, CheckActionInputSchema as Ci, DelegateInputSchema as Cn, issueName as Co, QUESTION_TOOL_VIA as Cr, MigrationStepIdSchema as Ct, DeviationIdInputSchema as D, CompleteDeferredInputSchema as Da, PUBLICATION_DOCS_DIR as Di, CritiqueInputSchema as Dn, GitBranchSchema as Do, BUDGET_EVENT_TYPES as Dr, MERGE_ORDER_EVENT_TYPES as Dt, DEVIATION_EVENT_TYPES as E, plainLine as Ea, SESSION_EVENT_TYPES as Ei, CRITIQUE_EVENT_TYPES as En, GIT_MERGE_MODES as Eo, QuestionToolViaSchema as Er, GetMergeOrderInputSchema as Et, deviationIdOf as F, hasControlCharacter as Fa, MEMORY_EVENT_TYPES as Fi, isCritiqueGesture as Fn, ULID_PATTERN as Fo, MAX_QA_PROOF_BYTES as Fr, JOURNAL_DIR as Ft, THREE_VIEWS_AGENTS as G, AgentIdSchema as Ga, retroIdOf as Gi, DOCUMENT_ID_PATTERN as Gn, REQUIREMENT_EVENT_TYPES as Gr, PresenceCredentialSchema as Gt, RoundTableInputSchema as H, StoryIdSchema as Ha, actionIdOf as Hi, ArchitectureHeaderSchema as Hn, RecordCriterionInputSchema as Hr, HUMAN_DEVICE_PATTERN as Ht, ReviewTestsInputSchema as I, AGENT_EVENT_TYPES as Ia, PROMOTION_TITLE as Ii, isHypothesisOutcome as In, eventInvalid as Io, QA_COMMAND as Ir, JOURNAL_REF as It, AnswerStackInputSchema as J, DEFAULT_VALIDATION_MODE as Ja, CREATOR_ACTION_EVENT_TYPES as Ji, EPIC_DOCUMENT_ID_PATTERN as Jn, SubmitAuditInputSchema as Jr, FAST_TRACK_OPTIONS as Jt, flatTable as K, DEFAULT_INSTALL_MODE as Ka, retroTitle as Ki, DOCUMENT_KINDS as Kn, RecordProofInputSchema as Kr, FAST_TRACK_ENGINE_REASONS as Kt, TEST_CRITERIA as L, MAIN_SESSION_AGENT as La, PromoteLessonInputSchema as Li, COURSE_EVENT_TYPES as Ln, jsonValueIssue as Lo, QA_EVENT_TYPES as Lr, MACHINE_DEVICE_PATTERN as Lt, ResolveDeviationInputSchema as M, canonicalStoryId as Ma, CommonLessonIdSchema as Mi, critiquePathOf as Mn, EVENT_REASON as Mo, AgentFrontmatterSchema as Mr, LOOP_EVENT_TYPES as Mt, TRACED_REQUIREMENT_PATTERN as N, compareStoryIds as Na, LESSON_EVENT_TYPES as Ni, critiqueStepOf as Nn, EVENT_TYPE_PATTERN as No, SkillFrontmatterSchema as Nr, LoopInputSchema as Nt, DeviationReasonSchema as O, DEFERRED_EVENT_TYPES as Oa, PUBLICATION_EXPORT_DIR as Oi, IDEA_VERSION as On, GitWorkflowSchema as Oo, BUDGET_TITLE_PREFIX as Or, QualifyMergeConflictInputSchema as Ot, TracedRequirementInputSchema as P, deferredIdOf as Pa, MEMORY_APPROVING_DECISIONS as Pi, hypothesisIdOf as Pn, SIGNATURE_REASON as Po, AcceptanceQaInputSchema as Pr, EVENT_TYPES as Pt, stackIdOf as Q, INSTALL_MODES as Qa, FORGE_CREATOR_ACTIONS as Qi, REQUIREMENT_ID_PATTERN as Qn, REVIEW_EVENT_TYPES as Qr, DEFAULT_MERGE_METHOD as Qt, TEST_REVIEW_EVENT_TYPES as R, NextInputSchema as Ra, ProposePromotionInputSchema as Ri, COURSE_KINDS as Rn, parseEventEnvelope as Ro, QA_PROOF_DIR as Rr, RESERVED_EVENT_TYPES as Rt, MeasureUsageInputSchema as S, contentSections as Sa, checkpointIdOf as Si, DELEGATION_EVENT_TYPES as Sn, ISSUE as So, QUESTION_TOOL_NAME as Sr, METHOD_EVENT_TYPES as St, UsageMeasuredPayloadSchema as T, isSkillOverrideContent as Ta, RequestActionInputSchema as Ti, CRITIQUE_DOCUMENT_KINDS as Tn, GIT_EVENT_TYPES as To, QuestionStackProofSchema as Tr, CONFLICT_OPTIONS as Tt, TABLE_ANGLES as U, SubmitResultInputSchema as Ua, commonIdOf as Ui, CREATOR_OWNER as Un, criteriaInLine as Ur, HUMAN_EVENT_TYPES as Ut, RoomSchema as V, ReportStatusInputSchema as Va, RecordLessonsInputSchema as Vi, parseCourseStart as Vn, CRITERION_EVENT_TYPES as Vr, journalSeenRef as Vt, TABLE_EVENT_TYPES as W, UpdateStoryInputSchema as Wa, lessonIdOf as Wi, DOCUMENT_DIRS as Wn, criterionId as Wr, HUMAN_ONLY_EVENT_TYPES as Wt, OpenStackInputSchema as X, DEVELOPER_SPECIALTIES as Xa, CreatorActionSchema as Xi, KNOWLEDGE_SOURCES_FIELD as Xn, JUDGE_LENS as Xr, StartStoryFastTrackInputSchema as Xt, AskStackInputSchema as Y, DEFAULT_VALIDATION_POINTS as Ya, CREATOR_ACTION_KINDS as Yi, EpicHeaderSchema as Yn, SyncRequirementsInputSchema as Yr, LeaveFastTrackInputSchema as Yt, STACK_EVENT_TYPES as Z, DOMAIN_IDS as Za, FILE_CREATOR_ACTIONS as Zi, KnowledgeSourceSchema as Zn, REVIEW_DOMAINS as Zr, missingDeclarations as Zt, currentEvent as _, AgentContentSchema as _a, NOTE_REQUIRED as _i, devicePairedRecord as _n, INHERIT_MODEL as _o, AnswerCheckpointInputSchema as _r, ReadSensitiveInputSchema as _t, sha256 as a, REVIEW_SKILLS as aa, findingIdOf as ai, RepositoryPathSchema as an, PullRequestSettingsSchema as ao, INSTALL_EVENT_TYPES as ar, READINESS_EVENT_TYPES as at, isSensitiveEventType as b, SKILL_OVERRIDE_FIELDS as ba, RESOLUTION_DECISIONS as bi, pairOfferRecord as bn, ModelTableSchema as bo, CHAT_RETENTION_MS as br, isSensitivePath as bt, fromUtf8 as c, RunProofCommandInputSchema as ca, reviewIdOf as ci, ELICITATION_TIMEOUT_MS as cn, RulesSchema as co, CheckCiInputSchema as cr, ReadyInputSchema as ct, utf8 as d, isStoryStep as da, STORY_EVENT_TYPES as di, BRIDGE_ID_PATTERN as dn, TEST_FILE_MAX_LINES as do, CiWaiverReasonSchema as dr, ENCRYPTED_FILE_PREFIX as dt, engineUpdateRisk as ea, SECURITY_REASONS as ei, KNOWLEDGE_STORY_FIELD as en, MethodConfigSchema as eo, STORY_SECTIONS as er, MAX_SPRINT_USD as et, ClaudeSettingsSchema as f, ARCHITECTURE_PREREQUISITES_FIELD as fa, isStoryBranch as fi, DEVICE_TRUST_EVENT_TYPES as fn, TOOL_IDS as fo, MAX_CI_CHECKS as fr, KEY_ID_PATTERN as ft, JOURNAL_PROJECTED_REF as g, PrerequisitesSchema as ga, LIFTING_DECISIONS as gi, deviceKindOf as gn, ValidationSettingsSchema as go, ANTICIPATED_ANSWER_WINDOW_MS as gr, PRODUCT_KEY_EXPORT_PREFIX as gt, GetStateInputSchema as h, PrerequisiteVersionSchema as ha, CheckpointIdSchema as hi, TRUST_SIGNATURE_TAGS as hn, VALIDATION_POINTS as ho, CHECKPOINT_CLOSED_EVENT_TYPES as hr, PRODUCT_KEY_EVENT_TYPES as ht, randomBytes as i, WRITE_TEXT_MAX as ia, TriageReviewInputSchema as ii, MERGE_EVENT_TYPES as in, ProfileSchema as io, DependenciesSchema as ir, sprintIdOf as it, ReportDeviationInputSchema as j, DeferredReasonSchema as ja, hasMisleadingControl as ji, critiqueIdOf as jn, sameWorkflow as jo, menuKey as jr, HYPOTHESIS_KIND as jt, GetTraceInputSchema as k, DeferWorkInputSchema as ka, countWords as ki, RECONCILE_KINDS as kn, recommendedGitWorkflow as ko, AgentDefinitionSchema as kr, SUBSTANTIVE_CLASSES as kt, toBase64url as l, normalizedFolder as la, CommitIdSchema as li, ElicitationChannelSchema as ln, SemverSchema as lo, CiReportedPayloadSchema as lr, readinessIdOf as lt, SyncManifestSchema as m, PrerequisiteCommandSchema as ma, CONFIRMATION_MODES as mi, RootPinSchema as mn, VALIDATION_MODES as mo, DecideCheckpointInputSchema as mr, PAYLOAD_ENCRYPTION_ALG as mt, WEBAUTHN_RP_ID as n, CheckWriteInputSchema as na, StoryReviewInputSchema as ni, KnowledgeStorySchema as nn, PHASE_IDS as no, epicIdOf as nr, SPRINT_EVENT_TYPES as nt, sha256Hex as o, SkillOverrideSchema as oa, isSilentJudge as oi, AskCreatorInputSchema as on, RULE_ACTIONS as oo, CI_EVENT_TYPES as or, READINESS_FIX_FIRST as ot, CursorCliConfigSchema as p, EnvironmentCheckIdSchema as pa, CHECKPOINT_EVENT_TYPES as pi, DEVICE_TRUST_SYSTEM_EVENT_TYPES as pn, TestSettingsSchema as po, CHECKPOINT_DECIDED_EVENT_TYPES as pr, MAX_SENSITIVE_CONTENT_BYTES as pt, roundTableStepOf as q, DEFAULT_PERMISSION_MODE as qa, scopeLabel as qi, DocumentHeaderSchema as qn, SettleGapInputSchema as qr, FAST_TRACK_EVENT_TYPES as qt, gitBlobId as r, WRITE_GUARD_EVENT_TYPES as ra, SubmitReviewInputSchema as ri, MAX_MERGED_FILES as rn, PROFILE_PRESET_IDS as ro, ARCHITECTURE_DEPENDENCIES_FIELD as rr, SPRINT_TITLE_PREFIX as rt, fromBase64url as s, ProofsFileSchema as sa, isSilentLens as si, ELICITATION_EVENT_TYPES as sn, RULE_VALUES as so, CI_FORGES as sr, READINESS_GO_ON as st, WEBAUTHN_ORIGIN as t, proofCommandsRisk as ta, StartReviewInputSchema as ti, KnowledgeCodeSchema as tn, PERMISSION_MODES as to, StoryHeaderSchema as tr, PlanSprintInputSchema as tt, toHex as u, PHASE_STEPS as ua, OpenPullRequestInputSchema as ui, MAX_ELICITED_NOTE_LENGTH as un, SkillNameSchema as uo, CiUrlSchema as ur, readinessStepOf as ut, eventTypeSpec as v, FORM_CONTENT_MAX_CHARS as va, OpenCheckpointInputSchema as vi, deviceRevokedRecord as vn, MODEL_CLIENT_IDS as vo, CHAT_DECISIONS as vr, SENSITIVE_AREAS as vt, USAGE_SOURCES as w, isSkillContent as wa, RecordExternalReadInputSchema as wi, NOAH_AGENT as wn, GIT_CI_MODES as wo, QuestionProofSchema as wr, CONFLICT_CLASSES as wt, isTestEventType as x, SkillContentSchema as xa, ReportMeasuresInputSchema as xi, rootCertifiedRecord as xn, NO_EFFORT as xo, MessageDigestSchema as xr, utf8Length as xt, eventTypeUpgrade as y, REQUIRED_CONTENT_FIELDS as ya, RECIPIENT_ROLES as yi, isTrustSignatureTag as yn, MODEL_TIERS as yo, CHAT_EVENT_TYPES as yr, WriteSensitiveInputSchema as yt, TEST_ROWS as z, ReportCostInputSchema as za, ProposeRetrospectiveInputSchema as zi, CourseInputSchema as zn, TEST_EVENT_TYPE as zo, qaProofBelongs as zr, journalEventPath as zt };
|
|
12873
|
+
export { readinessIdOf as $, AGENT_EVENT_TYPES as $a, PROMOTION_TITLE as $i, epicIdOf as $n, EVENT_REASON as $o, QA_COMMAND as $r, MAX_MERGED_FILES as $t, RoomIdSchema as A, EnvironmentCheckIdSchema as Aa, CHECKPOINT_EVENT_TYPES as Ai, critiqueStepOf as An, SkillNameSchema as Ao, deviceRevokedRecord as Ar, EVENT_TYPES as At, OpenStackInputSchema as B, contentSize as Ba, CheckActionInputSchema as Bi, CREATOR_OWNER as Bn, MODEL_TIERS as Bo, QUESTION_TOOL_NAME as Br, HUMAN_ONLY_EVENT_TYPES as Bt, USAGE_EVENT_TYPES as C, SkillOverrideSchema as Ca, isSilentJudge as Ci, CRITIQUE_EVENT_TYPES as Cn, PROFILE_PRESET_SPECS as Co, BRIDGE_ID_PATTERN as Cr, QualifyMergeConflictInputSchema as Ct, TEST_CRITERIA as D, PHASE_STEPS as Da, OpenPullRequestInputSchema as Di, SOLO_METHODS as Dn, RULE_VALUES as Do, TRUST_SIGNATURE_TAGS as Dr, HYPOTHESIS_KIND as Dt, ReviewTestsInputSchema as E, normalizedFolder as Ea, CommitIdSchema as Ei, RECONCILE_KINDS as En, RULE_ACTIONS as Eo, RootPinSchema as Er, HYPOTHESIS_CONFIDENCES as Et, THREE_VIEWS_AGENTS as F, FORM_CONTENT_MAX_CHARS as Fa, OpenCheckpointInputSchema as Fi, COURSE_KINDS as Fn, VALIDATION_POINTS as Fo, AnswerCheckpointInputSchema as Fr, journalEventPath as Ft, PlanSprintInputSchema as G, DEFERRED_EVENT_TYPES as Ga, PUBLICATION_EXPORT_DIR as Gi, EPIC_DOCUMENT_ID_PATTERN as Gn, GIT_CI_MODES as Go, RULE_LOCKS as Gr, LeaveFastTrackInputSchema as Gt, stackIdOf as H, isSkillOverrideContent as Ha, RequestActionInputSchema as Hi, DOCUMENT_ID_PATTERN as Hn, NO_EFFORT as Ho, QuestionProofSchema as Hr, FAST_TRACK_ENGINE_REASONS as Ht, flatTable as I, REQUIRED_CONTENT_FIELDS as Ia, RECIPIENT_ROLES as Ii, CourseInputSchema as In, ValidationSettingsSchema as Io, CHAT_DECISIONS as Ir, journalRemoteRef as It, sprintIdOf as J, DeferredReasonSchema as Ja, hasMisleadingControl as Ji, KnowledgeSourceSchema as Jn, GitBranchSchema as Jo, menuKey as Jr, DEFAULT_MERGE_METHOD as Jt, SPRINT_EVENT_TYPES as K, DeferWorkInputSchema as Ka, countWords as Ki, EpicHeaderSchema as Kn, GIT_EVENT_TYPES as Ko, AgentDefinitionSchema as Kr, StartStoryFastTrackInputSchema as Kt, roundTableStepOf as L, SKILL_OVERRIDE_FIELDS as La, RESOLUTION_DECISIONS as Li, courseIdOf as Ln, INHERIT_MODEL as Lo, CHAT_EVENT_TYPES as Lr, journalSeenRef as Lt, RoundTableInputSchema as M, PrerequisiteVersionSchema as Ma, CheckpointIdSchema as Mi, isCritiqueGesture as Mn, TOOL_IDS as Mo, pairOfferRecord as Mr, JOURNAL_REF as Mt, TABLE_ANGLES as N, PrerequisitesSchema as Na, LIFTING_DECISIONS as Ni, isHypothesisOutcome as Nn, TestSettingsSchema as No, rootCertifiedRecord as Nr, MACHINE_DEVICE_PATTERN as Nt, TEST_REVIEW_EVENT_TYPES as O, isStoryStep as Oa, STORY_EVENT_TYPES as Oi, critiqueIdOf as On, RulesSchema as Oo, deviceKindOf as Or, LOOP_EVENT_TYPES as Ot, TABLE_EVENT_TYPES as P, AgentContentSchema as Pa, NOTE_REQUIRED as Pi, COURSE_EVENT_TYPES as Pn, VALIDATION_MODES as Po, ANTICIPATED_ANSWER_WINDOW_MS as Pr, RESERVED_EVENT_TYPES as Pt, ReadyInputSchema as Q, hasControlCharacter as Qa, MEMORY_EVENT_TYPES as Qi, StoryHeaderSchema as Qn, sameWorkflow as Qo, MAX_QA_PROOF_BYTES as Qr, KnowledgeStorySchema as Qt, AnswerStackInputSchema as R, SkillContentSchema as Ra, ReportMeasuresInputSchema as Ri, parseCourseStart as Rn, LEGACY_GEMINI_CLIENT as Ro, CHAT_RETENTION_MS as Rr, HUMAN_DEVICE_PATTERN as Rt, MeasureUsageInputSchema as S, REVIEW_SKILLS as Sa, findingIdOf as Si, CRITIQUE_DOCUMENT_KINDS as Sn, PROFILE_PRESET_IDS as So, WEB_ANSWER_WINDOW_MS as Sr, MERGE_ORDER_EVENT_TYPES as St, UsageMeasuredPayloadSchema as T, RunProofCommandInputSchema as Ta, reviewIdOf as Ti, IDEA_VERSION as Tn, PullRequestSettingsSchema as To, DEVICE_TRUST_SYSTEM_EVENT_TYPES as Tr, MACHINE_EVENT_TYPES as Tt, MAX_SPRINT_CHECKPOINT_SUMMARY as U, plainLine as Ua, SESSION_EVENT_TYPES as Ui, DOCUMENT_KINDS as Un, ISSUE as Uo, QuestionStackProofSchema as Ur, FAST_TRACK_EVENT_TYPES as Ut, STACK_EVENT_TYPES as V, isSkillContent as Va, RecordExternalReadInputSchema as Vi, DOCUMENT_DIRS as Vn, ModelTableSchema as Vo, QUESTION_TOOL_VIA as Vr, PresenceCredentialSchema as Vt, MAX_SPRINT_USD as W, CompleteDeferredInputSchema as Wa, PUBLICATION_DOCS_DIR as Wi, DocumentHeaderSchema as Wn, issueName as Wo, QuestionToolViaSchema as Wr, FAST_TRACK_OPTIONS as Wt, READINESS_FIX_FIRST as X, compareStoryIds as Xa, LESSON_EVENT_TYPES as Xi, STORY_DOCUMENT_ID_PATTERN as Xn, recommendedGitWorkflow as Xo, SkillFrontmatterSchema as Xr, KNOWLEDGE_STORY_FIELD as Xt, READINESS_EVENT_TYPES as Y, canonicalStoryId as Ya, CommonLessonIdSchema as Yi, REQUIREMENT_ID_PATTERN as Yn, GitWorkflowSchema as Yo, AgentFrontmatterSchema as Yr, KNOWLEDGE_CODE_FIELD as Yt, READINESS_GO_ON as Z, deferredIdOf as Za, MEMORY_APPROVING_DECISIONS as Zi, STORY_SECTIONS as Zn, resolveGitWorkflow as Zo, AcceptanceQaInputSchema as Zr, KnowledgeCodeSchema as Zt, currentEvent as _, engineUpdateRisk as _a, SECURITY_REASONS as _i, TracedRequirementInputSchema as _n, INSTALL_MODES as _o, BUDGET_RECOMMENDATION as _r, METHOD_EVENT_TYPES as _t, fromBase64url as a, actionIdOf as aa, RecordCriterionInputSchema as ai, ELICITATION_EVENT_TYPES as an, StoryIdSchema as ao, CheckCiInputSchema as ar, parseEventEnvelope as as, PRODUCT_KEYS_EXPORT_PREFIX as at, isSensitiveEventType as b, WRITE_GUARD_EVENT_TYPES as ba, SubmitReviewInputSchema as bi, DelegateInputSchema as bn, PERMISSION_MODES as bo, WEB_ANSWER_EVENT_TYPES as br, CONFLICT_OPTIONS as bt, toHex as c, retroIdOf as ca, REQUIREMENT_EVENT_TYPES as ci, MAX_ELICITED_NOTE_LENGTH as cn, AgentIdSchema as co, CiWaiverReasonSchema as cr, TEST_EVENT_TYPE as cs, ProductKeyRotatedPayloadSchema as ct, ClaudeSettingsSchema as d, CREATOR_ACTION_EVENT_TYPES as da, SubmitAuditInputSchema as di, DeviationReasonSchema as dn, DEFAULT_ON_EXCEED as do, DecideCheckpointInputSchema as dr, WriteSensitiveInputSchema as dt, PromoteLessonInputSchema as ea, QA_EVENT_TYPES as ei, MERGE_EVENT_TYPES as en, MAIN_SESSION_AGENT as eo, ARCHITECTURE_DEPENDENCIES_FIELD as er, EVENT_TYPE_PATTERN as es, readinessStepOf as et, CursorCliConfigSchema as f, CREATOR_ACTION_KINDS as fa, SyncRequirementsInputSchema as fi, GetTraceInputSchema as fn, DEFAULT_PERMISSION_MODE as fo, CHECKPOINT_CLOSED_EVENT_TYPES as fr, isSensitivePath as ft, JOURNAL_PROJECTED_REF as g, RequestCreatorActionInputSchema as ga, REVIEW_LENSES as gi, TRACED_REQUIREMENT_PATTERN as gn, DOMAIN_IDS as go, BUDGET_OPTIONS as gr, TEAM_EVENT_TYPES as gt, REDACTED_SECRET as h, FORGE_CREATOR_ACTIONS as ha, REVIEW_EVENT_TYPES as hi, ResolveDeviationInputSchema as hn, DEVELOPER_SPECIALTIES as ho, BUDGET_EVENT_TYPES as hr, PauseTeamInputSchema as ht, sha256Hex as i, RecordLessonsInputSchema as ia, CRITERION_EVENT_TYPES as ii, AskCreatorInputSchema as in, ReportStatusInputSchema as io, CI_FORGES as ir, jsonValueIssue as is, PAYLOAD_ENCRYPTION_ALG as it, RoomSchema as j, PrerequisiteCommandSchema as ja, CONFIRMATION_MODES as ji, hypothesisIdOf as jn, TEST_FILE_MAX_LINES as jo, isTrustSignatureTag as jr, JOURNAL_DIR as jt, TEST_ROWS as k, ARCHITECTURE_PREREQUISITES_FIELD as ka, isStoryBranch as ki, critiquePathOf as kn, SemverSchema as ko, devicePairedRecord as kr, LoopInputSchema as kt, utf8 as l, retroTitle as la, RecordProofInputSchema as li, DEVIATION_EVENT_TYPES as ln, DEFAULT_ALERT_RATIO as lo, MAX_CI_CHECKS as lr, ErrorCode as ls, ReadSensitiveInputSchema as lt, GetStateInputSchema as m, FILE_CREATOR_ACTIONS as ma, REVIEW_DOMAINS as mi, ReportDeviationInputSchema as mn, DEFAULT_VALIDATION_POINTS as mo, CheckCallInputSchema as mr, ORDER_EVENT_TYPES as mt, randomBytes as n, ProposeRetrospectiveInputSchema as na, qaProofBelongs as ni, EXCHANGE_EVENT_TYPES as nn, ReportCostInputSchema as no, INSTALL_EVENT_TYPES as nr, ULID_PATTERN as ns, KEY_ID_PATTERN as nt, fromUtf8 as o, commonIdOf as oa, criteriaInLine as oi, ELICITATION_TIMEOUT_MS as on, SubmitResultInputSchema as oo, CiReportedPayloadSchema as or, extensionModuleOf as os, PRODUCT_KEY_EVENT_TYPES as ot, SyncManifestSchema as p, CreatorActionSchema as pa, JUDGE_LENS as pi, REQUIREMENT_STATUSES as pn, DEFAULT_VALIDATION_MODE as po, CHAIN_EVENT_TYPES as pr, utf8Length as pt, SPRINT_TITLE_PREFIX as q, DeferredIdInputSchema as qa, hasHiddenCharacter as qi, KNOWLEDGE_SOURCES_FIELD as qn, GIT_MERGE_MODES as qo, AgentOverrideSchema as qr, missingDeclarations as qt, sha256 as r, ReadMemoryInputSchema as ra, qaProofKind as ri, SummarizeInputSchema as rn, ReportProgressInputSchema as ro, CI_EVENT_TYPES as rr, eventInvalid as rs, MAX_SENSITIVE_CONTENT_BYTES as rt, toBase64url as s, lessonIdOf as sa, criterionId as si, ElicitationChannelSchema as sn, UpdateStoryInputSchema as so, CiUrlSchema as sr, isExtensionEventType as ss, PRODUCT_KEY_EXPORT_PREFIX as st, gitBlobId as t, ProposePromotionInputSchema as ta, QA_PROOF_DIR as ti, RepositoryPathSchema as tn, NextInputSchema as to, DependenciesSchema as tr, SIGNATURE_REASON as ts, ENCRYPTED_FILE_PREFIX as tt, historyCsv as u, scopeLabel as ua, SettleGapInputSchema as ui, DeviationIdInputSchema as un, DEFAULT_INSTALL_MODE as uo, CHECKPOINT_DECIDED_EVENT_TYPES as ur, SENSITIVE_AREAS as ut, eventTypeSpec as v, proofCommandsRisk as va, StartReviewInputSchema as vi, deviationIdOf as vn, InstallSettingsSchema as vo, BUDGET_TITLE_PREFIX as vr, MigrationStepIdSchema as vt, USAGE_SOURCES as w, ProofsFileSchema as wa, isSilentLens as wi, CritiqueInputSchema as wn, ProfileSchema as wo, DEVICE_TRUST_EVENT_TYPES as wr, SUBSTANTIVE_CLASSES as wt, isTestEventType as x, WRITE_TEXT_MAX as xa, TriageReviewInputSchema as xi, NOAH_AGENT as xn, PHASE_IDS as xo, WEB_ANSWER_STALE_MS as xr, GetMergeOrderInputSchema as xt, eventTypeUpgrade as y, CheckWriteInputSchema as ya, StoryReviewInputSchema as yi, DELEGATION_EVENT_TYPES as yn, MethodConfigSchema as yo, MAX_BUDGET_USD as yr, CONFLICT_CLASSES as yt, AskStackInputSchema as z, contentSections as za, checkpointIdOf as zi, ArchitectureHeaderSchema as zn, MODEL_CLIENT_IDS as zo, MessageDigestSchema as zr, HUMAN_EVENT_TYPES as zt };
|