cueline 0.2.2 → 0.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.
Files changed (51) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +35 -0
  4. package/README.ja.md +12 -12
  5. package/README.ko.md +12 -12
  6. package/README.md +12 -12
  7. package/README.zh-CN.md +12 -12
  8. package/README.zh-TW.md +12 -12
  9. package/dist/src/api-run-prune.d.ts +41 -0
  10. package/dist/src/api-run-prune.js +218 -0
  11. package/dist/src/api-run-prune.js.map +1 -0
  12. package/dist/src/api.d.ts +3 -0
  13. package/dist/src/api.js +3 -0
  14. package/dist/src/api.js.map +1 -1
  15. package/dist/src/cli/health-commands.js +35 -0
  16. package/dist/src/cli/health-commands.js.map +1 -1
  17. package/dist/src/cli/main.js +95 -3
  18. package/dist/src/cli/main.js.map +1 -1
  19. package/dist/src/cli/observation-commands.js +94 -1
  20. package/dist/src/cli/observation-commands.js.map +1 -1
  21. package/dist/src/diagnostics/offline-self-test.d.ts +23 -0
  22. package/dist/src/diagnostics/offline-self-test.js +163 -0
  23. package/dist/src/diagnostics/offline-self-test.js.map +1 -0
  24. package/dist/src/diagnostics/secret-audit.d.ts +25 -0
  25. package/dist/src/diagnostics/secret-audit.js +114 -0
  26. package/dist/src/diagnostics/secret-audit.js.map +1 -0
  27. package/dist/src/diagnostics/upgrade-preflight.d.ts +41 -0
  28. package/dist/src/diagnostics/upgrade-preflight.js +182 -0
  29. package/dist/src/diagnostics/upgrade-preflight.js.map +1 -0
  30. package/dist/src/observation/run-bundle.d.ts +27 -0
  31. package/dist/src/observation/run-bundle.js +55 -0
  32. package/dist/src/observation/run-bundle.js.map +1 -0
  33. package/dist/src/version.d.ts +1 -1
  34. package/dist/src/version.js +1 -1
  35. package/docs/1.0/ideas/machine-output-contracts.md +47 -0
  36. package/docs/1.0/ideas/node26-contract.md +50 -0
  37. package/docs/1.0/ideas/offline-self-test.md +52 -0
  38. package/docs/1.0/ideas/upgrade-preflight.md +46 -0
  39. package/docs/compatibility.md +5 -5
  40. package/package.json +12 -2
  41. package/schemas/cli-doctor.schema.json +77 -0
  42. package/schemas/cli-routing-explain.schema.json +88 -0
  43. package/schemas/cli-routing.schema.json +65 -0
  44. package/schemas/cli-run-audit-secrets.schema.json +72 -0
  45. package/schemas/cli-run-export.schema.json +410 -0
  46. package/schemas/cli-runs-prune.schema.json +93 -0
  47. package/scripts/artifact-integrity.mjs +114 -0
  48. package/scripts/release-check.mjs +118 -0
  49. package/scripts/validate-cli-contracts.mjs +59 -0
  50. package/scripts/validate-doc-versions.mjs +79 -0
  51. package/scripts/validate-node-support.mjs +118 -0
@@ -0,0 +1,182 @@
1
+ import { lstat } from "node:fs/promises";
2
+ import { listCueLineRuns, routingConfigPath } from "../api.js";
3
+ import { loadRoutingConfig } from "../router/config-loader.js";
4
+ import { defaultCueLineHome } from "../state/paths.js";
5
+ import { CUELINE_VERSION } from "../version.js";
6
+ const STABLE_SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
7
+ function parseStableVersion(value) {
8
+ const match = STABLE_SEMVER.exec(value);
9
+ if (match === null)
10
+ return undefined;
11
+ return {
12
+ major: Number(match[1]),
13
+ minor: Number(match[2]),
14
+ patch: Number(match[3]),
15
+ };
16
+ }
17
+ function compareVersion(left, right) {
18
+ return left.major - right.major || left.minor - right.minor || left.patch - right.patch;
19
+ }
20
+ function isNotFound(error) {
21
+ return (typeof error === "object" &&
22
+ error !== null &&
23
+ "code" in error &&
24
+ error.code === "ENOENT");
25
+ }
26
+ export async function collectUpgradePreflight(options) {
27
+ const environment = options.environment ?? process.env;
28
+ const nodeVersion = options.nodeVersion ?? process.versions.node;
29
+ const targetVersion = options.targetVersion;
30
+ const configPath = routingConfigPath(environment);
31
+ const home = defaultCueLineHome(environment);
32
+ const findings = [];
33
+ const current = parseStableVersion(CUELINE_VERSION);
34
+ const target = parseStableVersion(targetVersion);
35
+ if (target === undefined) {
36
+ findings.push({
37
+ code: "TARGET_VERSION_INVALID",
38
+ severity: "blocker",
39
+ surface: "version",
40
+ message: "Target version must be a stable semantic version such as 1.0.0.",
41
+ });
42
+ }
43
+ else if (current !== undefined && compareVersion(target, current) < 0) {
44
+ findings.push({
45
+ code: "TARGET_VERSION_DOWNGRADE",
46
+ severity: "blocker",
47
+ surface: "version",
48
+ message: "Upgrade preflight does not authorize a downgrade.",
49
+ });
50
+ }
51
+ else if (current !== undefined && compareVersion(target, current) === 0) {
52
+ findings.push({
53
+ code: "TARGET_VERSION_NOT_NEWER",
54
+ severity: "warning",
55
+ surface: "version",
56
+ message: "Target version is already installed.",
57
+ });
58
+ }
59
+ const nodeMajor = Number.parseInt(nodeVersion.split(".")[0] ?? "0", 10);
60
+ const nodeOk = Number.isSafeInteger(nodeMajor) && nodeMajor >= 22;
61
+ if (!nodeOk) {
62
+ findings.push({
63
+ code: "NODE_VERSION_UNSUPPORTED",
64
+ severity: "blocker",
65
+ surface: "node",
66
+ message: "Node.js 22 or newer is required before upgrading CueLine.",
67
+ });
68
+ }
69
+ let configValid = true;
70
+ try {
71
+ await loadRoutingConfig(configPath);
72
+ }
73
+ catch {
74
+ configValid = false;
75
+ findings.push({
76
+ code: "ROUTING_CONFIG_INVALID",
77
+ severity: "blocker",
78
+ surface: "config",
79
+ message: "The active routing configuration cannot be loaded by this version.",
80
+ });
81
+ }
82
+ let stateKind = "missing";
83
+ let statePrivate = null;
84
+ try {
85
+ const state = await lstat(home);
86
+ if (state.isSymbolicLink()) {
87
+ stateKind = "symlink";
88
+ findings.push({
89
+ code: "STATE_HOME_SYMLINK_UNSAFE",
90
+ severity: "blocker",
91
+ surface: "state",
92
+ message: "CueLine state home is a symbolic link; resolve it explicitly before upgrading.",
93
+ });
94
+ }
95
+ else if (state.isDirectory()) {
96
+ stateKind = "directory";
97
+ statePrivate = (state.mode & 0o077) === 0;
98
+ if (!statePrivate) {
99
+ findings.push({
100
+ code: "STATE_HOME_PERMISSIONS_UNSAFE",
101
+ severity: "blocker",
102
+ surface: "state",
103
+ message: "CueLine state home grants group or other permissions.",
104
+ });
105
+ }
106
+ }
107
+ else {
108
+ stateKind = "other";
109
+ findings.push({
110
+ code: "STATE_HOME_NOT_DIRECTORY",
111
+ severity: "blocker",
112
+ surface: "state",
113
+ message: "CueLine state home exists but is not a directory.",
114
+ });
115
+ }
116
+ }
117
+ catch (error) {
118
+ if (!isNotFound(error)) {
119
+ stateKind = "other";
120
+ findings.push({
121
+ code: "STATE_HOME_UNREADABLE",
122
+ severity: "blocker",
123
+ surface: "state",
124
+ message: "CueLine state home metadata cannot be read.",
125
+ });
126
+ }
127
+ }
128
+ let totalRuns = 0;
129
+ let nonTerminalRuns = 0;
130
+ let unreadableRuns = 0;
131
+ if (stateKind === "missing" || (stateKind === "directory" && statePrivate === true)) {
132
+ try {
133
+ const runs = await listCueLineRuns({ home, environment });
134
+ totalRuns = runs.length;
135
+ unreadableRuns = runs.filter((run) => !run.readable).length;
136
+ nonTerminalRuns = runs.filter((run) => run.readable &&
137
+ run.status !== "complete" &&
138
+ run.status !== "blocked" &&
139
+ run.status !== "cancelled").length;
140
+ if (unreadableRuns > 0) {
141
+ findings.push({
142
+ code: "RUN_EVIDENCE_UNREADABLE",
143
+ severity: "blocker",
144
+ surface: "runs",
145
+ message: "At least one persisted run cannot be read safely.",
146
+ });
147
+ }
148
+ if (nonTerminalRuns > 0) {
149
+ findings.push({
150
+ code: "NON_TERMINAL_RUNS_PRESENT",
151
+ severity: "blocker",
152
+ surface: "runs",
153
+ message: "Finish, block, or cancel every persisted run before upgrading.",
154
+ });
155
+ }
156
+ }
157
+ catch {
158
+ findings.push({
159
+ code: "RUN_STORE_UNREADABLE",
160
+ severity: "blocker",
161
+ surface: "runs",
162
+ message: "The persisted run store cannot be inspected safely.",
163
+ });
164
+ }
165
+ }
166
+ return {
167
+ schema: "cueline-upgrade-preflight/1",
168
+ version: CUELINE_VERSION,
169
+ targetVersion,
170
+ status: findings.some((finding) => finding.severity === "blocker")
171
+ ? "blocked"
172
+ : "ready",
173
+ checks: {
174
+ node: { version: nodeVersion, requirement: ">=22", ok: nodeOk },
175
+ config: { path: configPath, valid: configValid },
176
+ stateHome: { path: home, kind: stateKind, private: statePrivate },
177
+ runs: { total: totalRuns, nonTerminal: nonTerminalRuns, unreadable: unreadableRuns },
178
+ },
179
+ findings,
180
+ };
181
+ }
182
+ //# sourceMappingURL=upgrade-preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upgrade-preflight.js","sourceRoot":"","sources":["../../../src/diagnostics/upgrade-preflight.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAyChD,MAAM,aAAa,GAAG,4CAA4C,CAAC;AAEnE,SAAS,kBAAkB,CAAC,KAAa;IACvC,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACrC,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACvB,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACvB,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;KACxB,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,IAAmB,EAAE,KAAoB;IAC/D,OAAO,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;AAC1F,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,MAAM,IAAI,KAAK;QACd,KAA4B,CAAC,IAAI,KAAK,QAAQ,CAChD,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,OAAgC;IAEhC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC;IACvD,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;IACjE,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;IAC5C,MAAM,UAAU,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAA8B,EAAE,CAAC;IAE/C,MAAM,OAAO,GAAG,kBAAkB,CAAC,eAAe,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,kBAAkB,CAAC,aAAa,CAAC,CAAC;IACjD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,wBAAwB;YAC9B,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,SAAS;YAClB,OAAO,EAAE,iEAAiE;SAC3E,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,OAAO,KAAK,SAAS,IAAI,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACxE,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,0BAA0B;YAChC,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,SAAS;YAClB,OAAO,EAAE,mDAAmD;SAC7D,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,OAAO,KAAK,SAAS,IAAI,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1E,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,0BAA0B;YAChC,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,SAAS;YAClB,OAAO,EAAE,sCAAsC;SAChD,CAAC,CAAC;IACL,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,EAAE,CAAC;IAClE,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,0BAA0B;YAChC,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,MAAM;YACf,OAAO,EAAE,2DAA2D;SACrE,CAAC,CAAC;IACL,CAAC;IAED,IAAI,WAAW,GAAG,IAAI,CAAC;IACvB,IAAI,CAAC;QACH,MAAM,iBAAiB,CAAC,UAAU,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,WAAW,GAAG,KAAK,CAAC;QACpB,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,wBAAwB;YAC9B,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,QAAQ;YACjB,OAAO,EAAE,oEAAoE;SAC9E,CAAC,CAAC;IACL,CAAC;IAED,IAAI,SAAS,GAA0D,SAAS,CAAC;IACjF,IAAI,YAAY,GAAmB,IAAI,CAAC;IACxC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,KAAK,CAAC,cAAc,EAAE,EAAE,CAAC;YAC3B,SAAS,GAAG,SAAS,CAAC;YACtB,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,2BAA2B;gBACjC,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,OAAO;gBAChB,OAAO,EAAE,gFAAgF;aAC1F,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YAC/B,SAAS,GAAG,WAAW,CAAC;YACxB,YAAY,GAAG,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;YAC1C,IAAI,CAAC,YAAY,EAAE,CAAC;gBAClB,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,+BAA+B;oBACrC,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,OAAO;oBAChB,OAAO,EAAE,uDAAuD;iBACjE,CAAC,CAAC;YACL,CAAC;QACH,CAAC;aAAM,CAAC;YACN,SAAS,GAAG,OAAO,CAAC;YACpB,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,0BAA0B;gBAChC,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,OAAO;gBAChB,OAAO,EAAE,mDAAmD;aAC7D,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YACvB,SAAS,GAAG,OAAO,CAAC;YACpB,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,uBAAuB;gBAC7B,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,OAAO;gBAChB,OAAO,EAAE,6CAA6C;aACvD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,eAAe,GAAG,CAAC,CAAC;IACxB,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,SAAS,KAAK,WAAW,IAAI,YAAY,KAAK,IAAI,CAAC,EAAE,CAAC;QACpF,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;YAC1D,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC;YACxB,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC;YAC5D,eAAe,GAAG,IAAI,CAAC,MAAM,CAC3B,CAAC,GAAG,EAAE,EAAE,CACN,GAAG,CAAC,QAAQ;gBACZ,GAAG,CAAC,MAAM,KAAK,UAAU;gBACzB,GAAG,CAAC,MAAM,KAAK,SAAS;gBACxB,GAAG,CAAC,MAAM,KAAK,WAAW,CAC7B,CAAC,MAAM,CAAC;YACT,IAAI,cAAc,GAAG,CAAC,EAAE,CAAC;gBACvB,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,yBAAyB;oBAC/B,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,MAAM;oBACf,OAAO,EAAE,mDAAmD;iBAC7D,CAAC,CAAC;YACL,CAAC;YACD,IAAI,eAAe,GAAG,CAAC,EAAE,CAAC;gBACxB,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,2BAA2B;oBACjC,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,MAAM;oBACf,OAAO,EAAE,gEAAgE;iBAC1E,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,sBAAsB;gBAC5B,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,MAAM;gBACf,OAAO,EAAE,qDAAqD;aAC/D,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO;QACL,MAAM,EAAE,6BAA6B;QACrC,OAAO,EAAE,eAAe;QACxB,aAAa;QACb,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC;YAChE,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,OAAO;QACX,MAAM,EAAE;YACN,IAAI,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE;YAC/D,MAAM,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE;YAChD,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE;YACjE,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE;SACrF;QACD,QAAQ;KACT,CAAC;AACJ,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { CueLineRunVerificationReport, CueLineRuntimeOptions } from "../api-contracts.js";
2
+ import { safeCueLineRunStatus } from "../cli/run-status-view.js";
3
+ import { type CueLineRunDiagnosis } from "../diagnostics/run-doctor.js";
4
+ import { type CueLineRunTimeline } from "./run-timeline.js";
5
+ export declare const RUN_BUNDLE_PROTOCOL = "cueline-run-bundle/0.1";
6
+ export interface CueLineRunSupportBundle {
7
+ schema: "cueline-run-export/1";
8
+ protocol: typeof RUN_BUNDLE_PROTOCOL;
9
+ version: string;
10
+ generatedAt: string;
11
+ runId: string;
12
+ status: ReturnType<typeof safeCueLineRunStatus>;
13
+ verification: CueLineRunVerificationReport;
14
+ diagnosis: CueLineRunDiagnosis;
15
+ timeline: CueLineRunTimeline;
16
+ }
17
+ export interface CueLineRunSupportBundleOptions extends Pick<CueLineRuntimeOptions, "home" | "environment" | "now"> {
18
+ timelineLimit?: number;
19
+ }
20
+ /**
21
+ * One sanitized JSON document for bug reports and cross-machine support:
22
+ * metadata-only status, content-free verification, doctor diagnosis, and the
23
+ * hashed audit timeline. Every section reuses an existing sanitized surface;
24
+ * this module adds no new evidence projection of its own, so it cannot leak
25
+ * more than the surfaces it composes.
26
+ */
27
+ export declare function buildCueLineRunSupportBundle(runId: string, options?: CueLineRunSupportBundleOptions): Promise<CueLineRunSupportBundle>;
@@ -0,0 +1,55 @@
1
+ import { loadCueLineRunStatus } from "../api-runtime-lifecycle.js";
2
+ import { verifyCueLineRun } from "../api-run-verification.js";
3
+ import { safeCueLineRunStatus } from "../cli/run-status-view.js";
4
+ import { CueLineError } from "../core/errors.js";
5
+ import { runtimeEnvironment } from "../core/runtime.js";
6
+ import { diagnoseCueLineRunStatus, } from "../diagnostics/run-doctor.js";
7
+ import { defaultCueLineHome } from "../state/paths.js";
8
+ import { CUELINE_VERSION } from "../version.js";
9
+ import { loadCueLineRunTimeline } from "./run-timeline.js";
10
+ export const RUN_BUNDLE_PROTOCOL = "cueline-run-bundle/0.1";
11
+ /**
12
+ * One sanitized JSON document for bug reports and cross-machine support:
13
+ * metadata-only status, content-free verification, doctor diagnosis, and the
14
+ * hashed audit timeline. Every section reuses an existing sanitized surface;
15
+ * this module adds no new evidence projection of its own, so it cannot leak
16
+ * more than the surfaces it composes.
17
+ */
18
+ export async function buildCueLineRunSupportBundle(runId, options = {}) {
19
+ const timelineLimit = options.timelineLimit ?? 1000;
20
+ if (!Number.isSafeInteger(timelineLimit) ||
21
+ timelineLimit < 1 ||
22
+ timelineLimit > 1000) {
23
+ throw new CueLineError("RUN_BUNDLE_LIMIT_INVALID", "timelineLimit must be an integer between 1 and 1000.");
24
+ }
25
+ const environment = options.environment ?? runtimeEnvironment();
26
+ const home = options.home ?? defaultCueLineHome(environment);
27
+ const shared = {
28
+ home,
29
+ ...(options.environment === undefined ? {} : { environment: options.environment }),
30
+ };
31
+ const status = await loadCueLineRunStatus(runId, {
32
+ ...shared,
33
+ ...(options.now === undefined ? {} : { now: options.now }),
34
+ });
35
+ const [verification, timeline] = await Promise.all([
36
+ verifyCueLineRun(runId, {
37
+ ...shared,
38
+ ...(options.now === undefined ? {} : { now: options.now }),
39
+ }),
40
+ loadCueLineRunTimeline(runId, { ...shared, limit: timelineLimit }),
41
+ ]);
42
+ const generatedAt = (options.now === undefined ? new Date() : options.now()).toISOString();
43
+ return {
44
+ schema: "cueline-run-export/1",
45
+ protocol: RUN_BUNDLE_PROTOCOL,
46
+ version: CUELINE_VERSION,
47
+ generatedAt,
48
+ runId,
49
+ status: safeCueLineRunStatus(status),
50
+ verification,
51
+ diagnosis: diagnoseCueLineRunStatus(status),
52
+ timeline,
53
+ };
54
+ }
55
+ //# sourceMappingURL=run-bundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-bundle.js","sourceRoot":"","sources":["../../../src/observation/run-bundle.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EACL,wBAAwB,GAEzB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAA2B,MAAM,mBAAmB,CAAC;AAEpF,MAAM,CAAC,MAAM,mBAAmB,GAAG,wBAAwB,CAAC;AAmB5D;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,KAAa,EACb,OAAO,GAAmC,EAAE;IAE5C,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,IAAI,CAAC;IACpD,IACE,CAAC,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC;QACpC,aAAa,GAAG,CAAC;QACjB,aAAa,GAAG,IAAI,EACpB,CAAC;QACD,MAAM,IAAI,YAAY,CACpB,0BAA0B,EAC1B,sDAAsD,CACvD,CAAC;IACJ,CAAC;IACD,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,kBAAkB,EAAE,CAAC;IAChE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,kBAAkB,CAAC,WAAW,CAAC,CAAC;IAC7D,MAAM,MAAM,GAAG;QACb,IAAI;QACJ,GAAG,CAAC,OAAO,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;KACnF,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,KAAK,EAAE;QAC/C,GAAG,MAAM;QACT,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;KAC3D,CAAC,CAAC;IACH,MAAM,CAAC,YAAY,EAAE,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACjD,gBAAgB,CAAC,KAAK,EAAE;YACtB,GAAG,MAAM;YACT,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;SAC3D,CAAC;QACF,sBAAsB,CAAC,KAAK,EAAE,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC;KACnE,CAAC,CAAC;IACH,MAAM,WAAW,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAC3F,OAAO;QACL,MAAM,EAAE,sBAAsB;QAC9B,QAAQ,EAAE,mBAAmB;QAC7B,OAAO,EAAE,eAAe;QACxB,WAAW;QACX,KAAK;QACL,MAAM,EAAE,oBAAoB,CAAC,MAAM,CAAC;QACpC,YAAY;QACZ,SAAS,EAAE,wBAAwB,CAAC,MAAM,CAAC;QAC3C,QAAQ;KACT,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- export declare const CUELINE_VERSION = "0.2.2";
1
+ export declare const CUELINE_VERSION = "0.3.0";
@@ -1,2 +1,2 @@
1
- export const CUELINE_VERSION = "0.2.2";
1
+ export const CUELINE_VERSION = "0.3.0";
2
2
  //# sourceMappingURL=version.js.map
@@ -0,0 +1,47 @@
1
+ # Machine-output contracts experiment
2
+
3
+ ## Hypothesis and user value
4
+
5
+ Automation that consumes CueLine health output needs versioned, executable
6
+ contracts so a release cannot silently change field names, types, redaction or
7
+ nested structure.
8
+
9
+ ## Change boundary
10
+
11
+ - Adds explicit schema identifiers to `doctor --json` and `routing --json`;
12
+ preserves the existing `routing explain --json` identifier.
13
+ - Publishes strict JSON Schema 2020-12 contracts for those three outputs.
14
+ - Adds a real CLI contract validator to CI and a development-only Ajv
15
+ dependency; CueLine runtime dependencies remain empty.
16
+ - Does not change human output, routing decisions, browser behavior, provider
17
+ calls or durable state.
18
+
19
+ ## Acceptance and adversarial cases
20
+
21
+ 1. Real doctor, routing and routing-explain JSON validate against their schemas.
22
+ 2. Every public object uses `additionalProperties: false`.
23
+ 3. A secret-like top-level field is rejected.
24
+ 4. A runner `argv` field is rejected.
25
+ 5. Missing required fields and wrong schema versions are rejected.
26
+ 6. Valid and invalid routing configuration variants retain parseable output.
27
+
28
+ ## Verified evidence
29
+
30
+ - Targeted contract and CLI tests: 67 passed, 0 failed, including all three
31
+ degraded invalid-config variants.
32
+ - Contract validation command: 3 passed, 0 failed.
33
+ - Full test suite: 494 passed, 0 failed after updating four authoritative
34
+ machine-output snapshots for the new schema identifiers.
35
+ - TypeScript build: passed.
36
+ - Plugin validation: passed.
37
+ - `npm pack --dry-run`: passed; all three schema files and the validator are in
38
+ the package, while Ajv remains development-only.
39
+
40
+ ## Rollback
41
+
42
+ Revert this branch's commit. It owns the three schemas, validator, tests, Ajv
43
+ development dependency, CI step, schema identifiers and updated snapshots.
44
+
45
+ This experiment remains isolated on `codex/idea-machine-output-contract`. It
46
+ must not be merged into `main`; Vincent decides whether it is selected or
47
+ integrated.
@@ -0,0 +1,50 @@
1
+ # Node 26 support contract experiment
2
+
3
+ ## Hypothesis and user value
4
+
5
+ CueLine should preserve Node 22 as its minimum while proving current even-LTS
6
+ compatibility through one synchronized engines, CI and documentation contract.
7
+
8
+ ## Change boundary
9
+
10
+ - Expands the existing Ubuntu/macOS CI matrix from Node 22/24 to 22/24/26.
11
+ - Keeps `engines.node` at `>=22`.
12
+ - Synchronizes all five README development sections and the compatibility
13
+ matrix.
14
+ - Adds a read-only contract validator; it does not install, download or switch
15
+ Node versions.
16
+ - Does not change runtime behavior, browser control, routing, state or provider
17
+ access.
18
+
19
+ ## Acceptance and adversarial cases
20
+
21
+ 1. Engine requirement is exactly `>=22`.
22
+ 2. CI Node majors are exactly 22, 24 and 26 on Ubuntu and macOS.
23
+ 3. All localized README files and compatibility docs name the tested majors.
24
+ 4. Removing Node 26, leaving one README stale or changing engines makes the
25
+ validator fail.
26
+ 5. The current Node 26 runtime passes the complete local release-facing checks.
27
+
28
+ ## Verified evidence
29
+
30
+ - Node support validator: passed on Node 26.3.0.
31
+ - Targeted contract tests: 4 passed, 0 failed.
32
+ - Full test suite on Node 26.3.0: 494 passed, 0 failed.
33
+ - Fake smoke on Node 26.3.0: 6 passed, 0 failed.
34
+ - Shell install/reinstall/uninstall/foreign-file preservation: passed.
35
+ - npm tarball global install, skill install, doctor and uninstall: passed.
36
+ - TypeScript build, plugin validation and `npm pack --dry-run`: passed.
37
+
38
+ ## Remaining external verification
39
+
40
+ The workflow now requests Node 26 on both Ubuntu and macOS, but this isolated
41
+ branch has not been pushed. Therefore GitHub-hosted Node 26 results remain
42
+ unverified until Vincent selects and explicitly authorizes remote integration.
43
+
44
+ ## Rollback
45
+
46
+ Revert this branch's commit. It owns the CI matrix addition, support validator,
47
+ tests, documentation synchronization and this experiment record.
48
+
49
+ This experiment remains isolated on `codex/idea-node26-contract`. It must not be
50
+ merged into `main`; Vincent decides whether it is selected or integrated.
@@ -0,0 +1,52 @@
1
+ # Offline self-test experiment
2
+
3
+ ## Hypothesis and user value
4
+
5
+ An installed CueLine package should be able to prove that its controller loop,
6
+ process routing, durable state, final delivery, and run verifier work together
7
+ without opening a browser, contacting a provider, or touching the user's real
8
+ CueLine state.
9
+
10
+ ## Change boundary
11
+
12
+ - Adds `cueline self-test [--json]`.
13
+ - Uses an in-memory deterministic controller and the current Node executable.
14
+ - Creates two private temporary directories for run state and workspace, then
15
+ removes only those exact directories before returning.
16
+ - Does not read or write the configured `CUELINE_HOME` or `CUELINE_CONFIG`.
17
+ - Does not open a browser, use credentials, contact a provider, or make a
18
+ network request.
19
+ - Does not relax production controller provenance checks. The in-memory
20
+ adapter supplies fixed synthetic Pro-shaped evidence solely to exercise the
21
+ existing verifier; the report is explicitly marked `offline: true`.
22
+
23
+ ## Acceptance and adversarial cases
24
+
25
+ 1. The isolated controller completes exactly two rounds.
26
+ 2. One required local process job completes.
27
+ 3. The expected final delivery is recorded.
28
+ 4. `verifyCueLineRun` returns `verified` for the temporary run.
29
+ 5. A pre-existing `CUELINE_DEPTH` fails closed with
30
+ `NESTED_ROUTING_REJECTED` before temporary state is created.
31
+ 6. Unknown CLI arguments return exit code 2.
32
+ 7. JSON output contains neither temporary paths nor worker task content.
33
+ 8. Temporary-directory counts are unchanged across a real CLI invocation.
34
+
35
+ ## Verified evidence
36
+
37
+ - Targeted integration tests: 4 passed, 0 failed.
38
+ - Full test suite: 494 passed, 0 failed.
39
+ - TypeScript build: passed.
40
+ - Plugin validation: passed.
41
+ - Real `./bin/cueline self-test --json`: status `ok`; two controller rounds,
42
+ one completed job, final delivery true, durable verification true.
43
+ - Temporary cleanup check: 0 matching directories before and after.
44
+
45
+ ## Rollback
46
+
47
+ Revert this branch's commit. The feature owns only
48
+ `src/diagnostics/offline-self-test.ts`, its integration test and documentation,
49
+ plus the focused command/help additions in `src/cli/main.ts`.
50
+
51
+ This experiment remains isolated on `codex/idea-offline-self-test`. It must not
52
+ be merged into `main`; Vincent decides whether it is selected or integrated.
@@ -0,0 +1,46 @@
1
+ # Upgrade preflight experiment
2
+
3
+ ## Hypothesis and user value
4
+
5
+ Before replacing an installed CueLine version, operators need one read-only
6
+ command that proves the target is a valid forward upgrade and that local
7
+ configuration and durable state are safe to carry forward.
8
+
9
+ ## Change boundary
10
+
11
+ - Adds `cueline upgrade preflight --to <version> [--json]`.
12
+ - Reads the current Node version, active routing configuration, state-home file
13
+ type and permissions, and sanitized persisted run summaries.
14
+ - Never creates a state home, follows a state-home symlink, changes permissions,
15
+ migrates files, cancels runs, launches a browser, or contacts a provider.
16
+ - Accepts stable semantic target versions only; it does not authorize downgrade
17
+ or prerelease migration policy.
18
+
19
+ ## Blocking conditions
20
+
21
+ - Invalid or older target version.
22
+ - Node older than 22.
23
+ - Routing configuration unreadable by the installed version.
24
+ - State home is a symlink, non-directory, unreadable, or grants group/other
25
+ permissions.
26
+ - Any run is non-terminal or has unreadable durable evidence.
27
+
28
+ ## Verified evidence
29
+
30
+ - Targeted integration tests: 5 passed, 0 failed.
31
+ - Full test suite: 495 passed, 0 failed.
32
+ - TypeScript build: passed.
33
+ - Plugin validation: passed.
34
+ - Real `./bin/cueline upgrade preflight --to 1.0.0 --json`: status `ready`
35
+ with a missing isolated state home.
36
+ - The real CLI check verified that the missing state home remained absent.
37
+ - A persisted non-terminal run retained the exact event-log size and mtime
38
+ across preflight.
39
+
40
+ ## Rollback
41
+
42
+ Revert this branch's commit. It owns the preflight diagnostic, its tests and
43
+ documentation, plus focused health-command and help entries.
44
+
45
+ This experiment remains isolated on `codex/idea-upgrade-preflight`. It must not
46
+ be merged into `main`; Vincent decides whether it is selected or integrated.
@@ -2,9 +2,9 @@
2
2
 
3
3
  ## Runtime matrix
4
4
 
5
- | Surface | v0.1 status | Notes |
5
+ | Surface | Current status | Notes |
6
6
  |---|---|---|
7
- | Node.js 22+ ESM | Supported | Core protocol, state, router, runners, fake browser tests |
7
+ | Node.js 22+ ESM | Supported | CI: 22, 24, 26 on Ubuntu and macOS; core protocol, state, router, runners, fake browser tests |
8
8
  | Codex Node REPL + built-in Browser (IAB) | Required for live control | Provides the imported API with the authenticated ChatGPT tab/browser client |
9
9
  | Current Codex caller | Default local executor | Performs caller `advise`; caller `work` requires durable claim/start/heartbeat/result proof |
10
10
  | `codex` CLI | Required only by bundled process route | Double authorization plus `--ignore-user-config`; auth remains under `CODEX_HOME`, user MCP config is not loaded |
@@ -19,9 +19,9 @@ The npm package has no runtime dependencies. Development requires TypeScript and
19
19
 
20
20
  CueLine is designed for a conversation already authenticated by the user at `chatgpt.com`. CueLine neither handles credentials nor verifies plan entitlement. Before each controller turn it may switch the composer to `Pro`; that is its only automatic model switch. It also requires Pro evidence on the completed response.
21
21
 
22
- The v0.1 adapter relies on accessible textbox/button roles, attachment chips, and assistant-message markup in the current ChatGPT web UI. ChatGPT's automatic conversion of a long filled prompt into an attachment is supported. Deliberate file upload is not. UI changes can cause explicit `COMPOSER_MISSING`, `SEND_BUTTON_MISSING`, or response-timeout errors. A fake adapter test cannot prove that the current live page still matches.
22
+ The browser adapter relies on accessible textbox/button roles, attachment chips, and assistant-message markup in the current ChatGPT web UI. ChatGPT's automatic conversion of a long filled prompt into an attachment is supported. Deliberate file upload is not. UI changes can cause explicit `COMPOSER_MISSING`, `SEND_BUTTON_MISSING`, or response-timeout errors. A fake adapter test cannot prove that the current live page still matches.
23
23
 
24
- ## Supported in v0.1
24
+ ## Supported contract
25
25
 
26
26
  - one ChatGPT conversation per run
27
27
  - text controller observations and commands
@@ -40,7 +40,7 @@ The v0.1 adapter relies on accessible textbox/button roles, attachment chips, an
40
40
  - process status with resolved runner, PID, phase, last progress, and safely observed model/provider
41
41
  - injected fake browser/runner for offline tests
42
42
 
43
- ## Not supported in v0.1
43
+ ## Not supported
44
44
 
45
45
  - model switching other than CueLine selecting `Pro`
46
46
  - images, screenshots as controller input, audio, or binary payloads
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cueline",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "A ChatGPT web conversation directs; CueLine validates each command and hands durable advice or explicitly claimed work to the current Codex.",
5
5
  "keywords": [
6
6
  "chatgpt",
@@ -66,10 +66,20 @@
66
66
  "test:unit": "npm run build && node --test dist/test/unit/*.test.js",
67
67
  "test:integration": "npm run build && node --test dist/test/integration/*.test.js",
68
68
  "smoke:fake": "npm run build && node --test dist/test/smoke/fake-smoke.test.js",
69
- "validate:plugin": "node scripts/validate-plugin.mjs"
69
+ "validate:node-support": "node scripts/validate-node-support.mjs --json",
70
+ "validate:cli-contracts": "npm run build && node scripts/validate-cli-contracts.mjs",
71
+ "validate:plugin": "node scripts/validate-plugin.mjs",
72
+ "validate:docs": "node scripts/validate-doc-versions.mjs",
73
+ "test:docs": "node --test test/release/doc-version-guard.test.mjs",
74
+ "release:check": "node scripts/release-check.mjs",
75
+ "test:release-check": "node --test test/release/release-check.test.mjs",
76
+ "artifact:build": "node scripts/artifact-integrity.mjs build",
77
+ "artifact:verify": "node scripts/artifact-integrity.mjs verify",
78
+ "test:artifact": "node --test test/release/artifact-integrity.test.mjs"
70
79
  },
71
80
  "devDependencies": {
72
81
  "@types/node": "^22.20.1",
82
+ "ajv": "^8.20.0",
73
83
  "typescript": "^7.0.2"
74
84
  }
75
85
  }
@@ -0,0 +1,77 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://cueline.dev/schemas/cli-doctor.schema.json",
4
+ "title": "CueLine doctor machine output",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema", "version", "status", "node", "config", "home", "caller", "process", "findings"],
8
+ "properties": {
9
+ "schema": { "const": "cueline-doctor/1" },
10
+ "version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" },
11
+ "status": { "enum": ["ok", "degraded"] },
12
+ "node": {
13
+ "type": "object",
14
+ "additionalProperties": false,
15
+ "required": ["version", "ok", "requirement"],
16
+ "properties": {
17
+ "version": { "type": "string", "minLength": 1 },
18
+ "ok": { "type": "boolean" },
19
+ "requirement": { "const": ">=22" }
20
+ }
21
+ },
22
+ "config": {
23
+ "oneOf": [
24
+ {
25
+ "type": "object",
26
+ "additionalProperties": false,
27
+ "required": ["path", "valid"],
28
+ "properties": {
29
+ "path": { "type": "string", "minLength": 1 },
30
+ "valid": { "const": true }
31
+ }
32
+ },
33
+ {
34
+ "type": "object",
35
+ "additionalProperties": false,
36
+ "required": ["path", "valid", "errorCode"],
37
+ "properties": {
38
+ "path": { "type": "string", "minLength": 1 },
39
+ "valid": { "const": false },
40
+ "errorCode": { "const": "ROUTING_CONFIG_INVALID" }
41
+ }
42
+ }
43
+ ]
44
+ },
45
+ "home": { "type": "string", "minLength": 1 },
46
+ "caller": {
47
+ "type": "object",
48
+ "additionalProperties": false,
49
+ "required": ["ready", "enabledLanes"],
50
+ "properties": {
51
+ "ready": { "type": "boolean" },
52
+ "enabledLanes": { "type": "integer", "minimum": 0 }
53
+ }
54
+ },
55
+ "process": {
56
+ "type": "object",
57
+ "additionalProperties": false,
58
+ "required": ["availableLanes"],
59
+ "properties": {
60
+ "availableLanes": { "type": "integer", "minimum": 0 }
61
+ }
62
+ },
63
+ "findings": {
64
+ "type": "array",
65
+ "items": {
66
+ "type": "object",
67
+ "additionalProperties": false,
68
+ "required": ["code", "surface", "message"],
69
+ "properties": {
70
+ "code": { "type": "string", "minLength": 1 },
71
+ "surface": { "enum": ["node", "config", "caller"] },
72
+ "message": { "type": "string", "minLength": 1 }
73
+ }
74
+ }
75
+ }
76
+ }
77
+ }