@nurix/nustack 0.24.0 → 0.25.2
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/dist/commands/desktopAgent.js +1 -1
- package/dist/commands/doctor.js +8 -11
- package/dist/commands/obligationList.js +23 -49
- package/dist/commands/records.js +6 -88
- package/dist/commands/store.js +199 -0
- package/dist/commands/sync.js +2 -2
- package/dist/commands/toolkit.js +2 -2
- package/dist/lib/context/register.js +1 -1
- package/dist/lib/desktop-agents/checkout.js +1 -1
- package/dist/lib/desktop-agents/claudeRun.js +4 -4
- package/dist/lib/etna/reconcile.js +4 -4
- package/dist/lib/graph/library.js +1 -1
- package/dist/lib/harness/hookCheck.js +2 -8
- package/dist/lib/harness/storeWaivers.js +1 -4
- package/dist/lib/local_store/database.js +12 -92
- package/dist/lib/local_store/device_state.js +2 -2
- package/dist/lib/local_store/index.js +2 -2
- package/dist/lib/local_store/paths.js +0 -6
- package/dist/lib/local_store/routing.js +8 -44
- package/dist/lib/onboarding/folder_state.js +2 -2
- package/dist/lib/onboarding/init_fresh_folder.js +1 -1
- package/dist/lib/onboarding/manifest_writer.js +6 -6
- package/dist/lib/origin_guard.js +1 -1
- package/dist/lib/project_context.js +5 -5
- package/dist/lib/project_contract.js +2 -2
- package/dist/lib/records/drain.js +3 -153
- package/dist/lib/records/kinds.js +16 -0
- package/dist/lib/records/recordInput.js +1 -1
- package/dist/lib/records/recordsTable.js +119 -161
- package/dist/lib/records/refusalLog.js +7 -7
- package/dist/lib/sessions/brief.js +1 -1
- package/dist/lib/sessions/sessionEvents.js +5 -49
- package/dist/lib/store/open.js +136 -0
- package/dist/lib/store/wipe.js +34 -0
- package/dist/lib/sync/apply.js +113 -0
- package/dist/lib/sync/client.js +327 -0
- package/dist/lib/sync/endpoint.js +26 -0
- package/dist/lib/sync/manifest.js +80 -0
- package/dist/lib/sync/outbox.js +104 -0
- package/dist/machine.sql +934 -0
- package/dist/program.js +8 -6
- package/dist/records.schema.json +4 -0
- package/dist/spec_schema.json +5 -5
- package/dist/vendor/harness/rules/docsRoot.js +2 -2
- package/dist/vendor/harness/rules/index.js +1 -1
- package/package.json +2 -2
- package/dist/lib/local_store/migrations.js +0 -399
- package/dist/lib/records/door.js +0 -79
package/dist/program.js
CHANGED
|
@@ -34,7 +34,8 @@ import { runHarnessMount, runHarnessStatus } from "./commands/harness.js";
|
|
|
34
34
|
import { runCheck } from "./commands/check.js";
|
|
35
35
|
import { runSessionsList, runSessionsPrune, runSessionsRecord, runSessionsServe, } from "./commands/sessions.js";
|
|
36
36
|
import { registerReviewCommands } from "./commands/review.js";
|
|
37
|
-
import { registerRecordsCommands
|
|
37
|
+
import { registerRecordsCommands } from "./commands/records.js";
|
|
38
|
+
import { registerStoreCommands, startDrainIfDue } from "./commands/store.js";
|
|
38
39
|
import { registerSessionCommands } from "./commands/session.js";
|
|
39
40
|
import { isPartCheckStale, startTelemetryReconcile } from "./lib/telemetry/part_state.js";
|
|
40
41
|
import { RECORD_ALIAS_GROUPS, registerRecordAliases } from "./commands/recordAliases.js";
|
|
@@ -92,7 +93,7 @@ program
|
|
|
92
93
|
program
|
|
93
94
|
.command("sync")
|
|
94
95
|
.description("Project this Project's Service credentials into the managed .env block (not Documents or Git)")
|
|
95
|
-
.option("--project <id>", "the Project to act as (defaults to nustack.service.json /
|
|
96
|
+
.option("--project <id>", "the Project to act as (defaults to nustack.service.json / NUSTACK_PROJECT_ID)")
|
|
96
97
|
.option("--json", "emit the versioned machine envelope instead of interactive output")
|
|
97
98
|
.action(async (options) => runSync({ ...program.opts(), ...options }));
|
|
98
99
|
program
|
|
@@ -260,10 +261,11 @@ registerProjectSetCommand(project, () => program.opts());
|
|
|
260
261
|
registerActivityCommand(project, () => program.opts());
|
|
261
262
|
registerProjectSyncCommands(project, () => program.opts());
|
|
262
263
|
registerReviewCommands(program.command("review").description("Read and mirror NuStack Review items"), () => program.opts());
|
|
263
|
-
registerRecordsCommands(program.command("records").description("Emit
|
|
264
|
+
registerRecordsCommands(program.command("records").description("Emit and list harness records"), () => program.opts());
|
|
265
|
+
registerStoreCommands(program.command("store").description("Sync this machine's NuStack database"), () => program.opts());
|
|
264
266
|
registerRecordAliases(program, () => program.opts());
|
|
265
267
|
const LOCAL_ONLY_COMMANDS = ["census", "check"];
|
|
266
|
-
const UNDRAINED_GROUPS = ["records", "gate", "session", "read", "lane", ...RECORD_ALIAS_GROUPS];
|
|
268
|
+
const UNDRAINED_GROUPS = ["records", "store", "gate", "session", "read", "lane", ...RECORD_ALIAS_GROUPS];
|
|
267
269
|
const HOOK_PATH_COMMANDS = ["context"];
|
|
268
270
|
program.hook("preAction", async (_program, action) => {
|
|
269
271
|
if (program.opts().nonInteractive)
|
|
@@ -363,7 +365,7 @@ harness
|
|
|
363
365
|
const toolkit = program.command("toolkit").description("Inspect and reconcile the Project's toolkit pin");
|
|
364
366
|
toolkit
|
|
365
367
|
.command("status")
|
|
366
|
-
.description("Compare the cloud pin against .nustack/
|
|
368
|
+
.description("Compare the cloud pin against .nustack/project.json and nustack.service.json (F-026)")
|
|
367
369
|
.option("--project <id>", "the Project to act as (defaults to the bound repository)")
|
|
368
370
|
.option("--json", "emit the versioned machine envelope")
|
|
369
371
|
.action(async (options) => runToolkitStatus(options, program.opts()));
|
|
@@ -467,7 +469,7 @@ lane.command("remove")
|
|
|
467
469
|
function describeError(error) {
|
|
468
470
|
const directCode = error?.code;
|
|
469
471
|
if (directCode === "PROJECT_CONTEXT_MISSING") {
|
|
470
|
-
return "no Project in context — run inside a bound repository (`.nustack/
|
|
472
|
+
return "no Project in context — run inside a bound repository (`.nustack/project.json`) or pass `--project <id>`.";
|
|
471
473
|
}
|
|
472
474
|
if (directCode === "ORGANIZATION_CONTEXT_MISSING") {
|
|
473
475
|
return "no Organization in context — run `nustack organization use <id>` or pass `--organization <id>`.";
|
package/dist/records.schema.json
CHANGED
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"type": "string",
|
|
49
49
|
"enum": [
|
|
50
50
|
"open",
|
|
51
|
+
"in_progress",
|
|
51
52
|
"removed"
|
|
52
53
|
]
|
|
53
54
|
}
|
|
@@ -129,6 +130,7 @@
|
|
|
129
130
|
"type": "string",
|
|
130
131
|
"enum": [
|
|
131
132
|
"open",
|
|
133
|
+
"in_progress",
|
|
132
134
|
"removed"
|
|
133
135
|
]
|
|
134
136
|
}
|
|
@@ -1508,6 +1510,7 @@
|
|
|
1508
1510
|
"type": "string",
|
|
1509
1511
|
"enum": [
|
|
1510
1512
|
"open",
|
|
1513
|
+
"in_progress",
|
|
1511
1514
|
"removed"
|
|
1512
1515
|
]
|
|
1513
1516
|
}
|
|
@@ -2375,6 +2378,7 @@
|
|
|
2375
2378
|
"type": "string",
|
|
2376
2379
|
"enum": [
|
|
2377
2380
|
"open",
|
|
2381
|
+
"in_progress",
|
|
2378
2382
|
"removed"
|
|
2379
2383
|
]
|
|
2380
2384
|
}
|
package/dist/spec_schema.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"description": "Manifest grammar v1 for an NuStack repo. Declares repo identity plus one or more service entries.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["spec", "name", "description", "repository", "services"],
|
|
8
|
-
"not": { "required": ["nustackApp", "
|
|
8
|
+
"not": { "required": ["nustackApp", "nustackProject"] },
|
|
9
9
|
"additionalProperties": false,
|
|
10
10
|
"properties": {
|
|
11
11
|
"$schema": { "type": "string" },
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
"workspaceId": { "type": "string", "minLength": 1 }
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
|
-
"
|
|
42
|
+
"nustackProject": {
|
|
43
43
|
"type": "object",
|
|
44
44
|
"additionalProperties": false,
|
|
45
|
-
"required": ["v", "
|
|
46
|
-
"description": "The v2
|
|
45
|
+
"required": ["v", "projectId", "organizationId"],
|
|
46
|
+
"description": "The v2 Project binding the CLI writes. `projectId` names the Project; nustackApp's `workspaceId` names the tenant. A manifest carrying both blocks is refused.",
|
|
47
47
|
"properties": {
|
|
48
48
|
"v": { "const": 2 },
|
|
49
|
-
"
|
|
49
|
+
"projectId": { "type": "string", "minLength": 1 },
|
|
50
50
|
"organizationId": { "type": "string", "minLength": 1 }
|
|
51
51
|
}
|
|
52
52
|
},
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// packages/harness/src/rules/docsRoot.ts - The repository's docs root: `.nustack/
|
|
1
|
+
// packages/harness/src/rules/docsRoot.ts - The repository's docs root: `.nustack/project.json` docsRoot, else
|
|
2
2
|
// `.claude/harness.json` docsRoot, else `docs`. One function for the rules and the mount, applying the declaration
|
|
3
3
|
// rule the `project-contract` conformance seam binds every reader to. docs/architecture/systems/harness-records.md
|
|
4
4
|
// speaks for it.
|
|
5
5
|
export const DEFAULT_DOCS_ROOT = 'docs';
|
|
6
|
-
export const DOCS_ROOT_SOURCES = ['.nustack/
|
|
6
|
+
export const DOCS_ROOT_SOURCES = ['.nustack/project.json', '.claude/harness.json'];
|
|
7
7
|
/** A `docsRoot` value as a declaration: trimmed, trailing slashes stripped; empty, absolute or `..`-segmented is none. */
|
|
8
8
|
export function docsRootDeclaration(value) {
|
|
9
9
|
if (typeof value !== 'string')
|
|
@@ -59,7 +59,7 @@ export const RULE_ALIASES = {
|
|
|
59
59
|
const SESSION_RULES = ['branch-worktree', 'live-command', 'commit-claim', 'foreign-claim', 'open-denials', 'exit-stop', 'queue'];
|
|
60
60
|
/** Change rules judge trees and a change set; session rules judge one session's action or facts, at the hook. */
|
|
61
61
|
export const RULE_SCOPES = Object.fromEntries(RULE_IDS.map((id) => [id, SESSION_RULES.includes(id) ? 'session' : 'change']));
|
|
62
|
-
const DOCS_SOURCES = ['.nustack/
|
|
62
|
+
const DOCS_SOURCES = ['.nustack/project.json', '.claude/harness.json'];
|
|
63
63
|
const DIRECT_READS = {
|
|
64
64
|
ownership: ['**'],
|
|
65
65
|
'plan-gate': [...DOCS_SOURCES, '@docs/history/plans/*.md'],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nurix/nustack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.2",
|
|
4
4
|
"description": "The nustack CLI — bootstrap NuStack services into a repo via the NuStack discovery plane.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@types/node": "^22.19.20",
|
|
35
35
|
"typescript": "^5.7.2",
|
|
36
36
|
"vitest": "^4.1.11",
|
|
37
|
-
"@nurix/contracts": "
|
|
37
|
+
"@nurix/contracts": "3.0.1",
|
|
38
38
|
"@nurix/nustack.studio": "0.0.0",
|
|
39
39
|
"@nurix/nustack.ui": "0.0.0",
|
|
40
40
|
"@nurix/harness": "0.0.0"
|
|
@@ -1,399 +0,0 @@
|
|
|
1
|
-
const MIGRATION_V1 = {
|
|
2
|
-
version: 1,
|
|
3
|
-
statements: [
|
|
4
|
-
`CREATE TABLE local_device_state (
|
|
5
|
-
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
6
|
-
device_ref TEXT,
|
|
7
|
-
api_base_url TEXT,
|
|
8
|
-
active_user_id TEXT,
|
|
9
|
-
active_user_email TEXT,
|
|
10
|
-
keychain_service TEXT,
|
|
11
|
-
keychain_account TEXT,
|
|
12
|
-
schema_version INTEGER NOT NULL DEFAULT 1,
|
|
13
|
-
last_auth_refresh_at_ms INTEGER,
|
|
14
|
-
created_at_ms INTEGER NOT NULL,
|
|
15
|
-
updated_at_ms INTEGER NOT NULL
|
|
16
|
-
)`,
|
|
17
|
-
`CREATE TABLE local_bindings (
|
|
18
|
-
id TEXT PRIMARY KEY,
|
|
19
|
-
app_id TEXT NOT NULL,
|
|
20
|
-
workspace_id TEXT NOT NULL,
|
|
21
|
-
repository_root TEXT NOT NULL,
|
|
22
|
-
git_dir TEXT NOT NULL,
|
|
23
|
-
git_common_dir TEXT NOT NULL,
|
|
24
|
-
repository_fingerprint TEXT NOT NULL,
|
|
25
|
-
bound_branch TEXT NOT NULL,
|
|
26
|
-
last_processed_commit_id TEXT,
|
|
27
|
-
last_applied_app_sequence INTEGER NOT NULL DEFAULT 0,
|
|
28
|
-
whitelist_manifest_id TEXT,
|
|
29
|
-
whitelist_manifest_sha256 TEXT,
|
|
30
|
-
whitelist_cache_path TEXT,
|
|
31
|
-
etna_profile_json TEXT CHECK (etna_profile_json IS NULL OR json_valid(etna_profile_json)),
|
|
32
|
-
status TEXT NOT NULL CHECK (status IN ('creating','active','paused','failed','removed')),
|
|
33
|
-
last_error_code TEXT,
|
|
34
|
-
last_error_detail TEXT,
|
|
35
|
-
created_at_ms INTEGER NOT NULL,
|
|
36
|
-
updated_at_ms INTEGER NOT NULL
|
|
37
|
-
)`,
|
|
38
|
-
`CREATE UNIQUE INDEX local_bindings_repository_root_live_unique
|
|
39
|
-
ON local_bindings (repository_root)
|
|
40
|
-
WHERE status IN ('creating','active','paused')`,
|
|
41
|
-
`CREATE INDEX local_bindings_app_idx ON local_bindings (app_id)`,
|
|
42
|
-
`CREATE TABLE local_commit_origins (
|
|
43
|
-
binding_id TEXT NOT NULL REFERENCES local_bindings(id),
|
|
44
|
-
commit_id TEXT NOT NULL,
|
|
45
|
-
handoff_id TEXT NOT NULL,
|
|
46
|
-
origin TEXT NOT NULL CHECK (origin = 'cloud_handoff'),
|
|
47
|
-
created_at_ms INTEGER NOT NULL,
|
|
48
|
-
PRIMARY KEY (binding_id, commit_id)
|
|
49
|
-
)`,
|
|
50
|
-
`CREATE TABLE inbound_handoff_applications (
|
|
51
|
-
handoff_id TEXT NOT NULL,
|
|
52
|
-
binding_id TEXT NOT NULL REFERENCES local_bindings(id),
|
|
53
|
-
state TEXT NOT NULL CHECK (state IN (
|
|
54
|
-
'received','evaluating','no_op','technical_wait','decision_required',
|
|
55
|
-
'approved','applying','committed','undo_requested','reverted',
|
|
56
|
-
'revert_conflict','superseded'
|
|
57
|
-
)),
|
|
58
|
-
manifest_sha256 TEXT,
|
|
59
|
-
base_manifest_json TEXT CHECK (base_manifest_json IS NULL OR json_valid(base_manifest_json)),
|
|
60
|
-
target_manifest_json TEXT CHECK (target_manifest_json IS NULL OR json_valid(target_manifest_json)),
|
|
61
|
-
local_state_fingerprint TEXT,
|
|
62
|
-
technical_wait_reason TEXT,
|
|
63
|
-
decision_state TEXT,
|
|
64
|
-
approved_by_user_id TEXT,
|
|
65
|
-
generated_commit_id TEXT,
|
|
66
|
-
notification_state TEXT,
|
|
67
|
-
recovery_bundle_id TEXT,
|
|
68
|
-
revert_commit_id TEXT,
|
|
69
|
-
superseded_by_handoff_id TEXT,
|
|
70
|
-
error_code TEXT,
|
|
71
|
-
error_detail TEXT,
|
|
72
|
-
created_at_ms INTEGER NOT NULL,
|
|
73
|
-
updated_at_ms INTEGER NOT NULL,
|
|
74
|
-
PRIMARY KEY (handoff_id, binding_id)
|
|
75
|
-
)`,
|
|
76
|
-
`CREATE TABLE local_recovery_bundles (
|
|
77
|
-
id TEXT PRIMARY KEY,
|
|
78
|
-
binding_id TEXT NOT NULL,
|
|
79
|
-
handoff_id TEXT NOT NULL,
|
|
80
|
-
bundle_path TEXT NOT NULL,
|
|
81
|
-
manifest_sha256 TEXT NOT NULL,
|
|
82
|
-
state TEXT NOT NULL CHECK (state IN ('active','restored','expired','deleted')),
|
|
83
|
-
created_at_ms INTEGER NOT NULL,
|
|
84
|
-
expires_at_ms INTEGER NOT NULL
|
|
85
|
-
)`,
|
|
86
|
-
`CREATE INDEX local_recovery_bundles_binding_idx ON local_recovery_bundles (binding_id)`,
|
|
87
|
-
`CREATE TABLE pending_uploads (
|
|
88
|
-
binding_id TEXT NOT NULL,
|
|
89
|
-
commit_id TEXT NOT NULL,
|
|
90
|
-
manifest_sha256 TEXT,
|
|
91
|
-
whitelist_manifest_sha256 TEXT,
|
|
92
|
-
idempotency_key TEXT NOT NULL,
|
|
93
|
-
attempts INTEGER NOT NULL DEFAULT 0,
|
|
94
|
-
next_retry_at_ms INTEGER,
|
|
95
|
-
last_error_code TEXT,
|
|
96
|
-
last_error_detail TEXT,
|
|
97
|
-
created_at_ms INTEGER NOT NULL,
|
|
98
|
-
updated_at_ms INTEGER NOT NULL,
|
|
99
|
-
PRIMARY KEY (binding_id, commit_id)
|
|
100
|
-
)`,
|
|
101
|
-
`CREATE UNIQUE INDEX pending_uploads_idempotency_key_unique
|
|
102
|
-
ON pending_uploads (idempotency_key)`,
|
|
103
|
-
`CREATE TABLE cached_app_events (
|
|
104
|
-
binding_id TEXT NOT NULL,
|
|
105
|
-
app_sequence INTEGER NOT NULL,
|
|
106
|
-
event_type TEXT NOT NULL,
|
|
107
|
-
payload_json TEXT CHECK (payload_json IS NULL OR json_valid(payload_json)),
|
|
108
|
-
applied_at_ms INTEGER,
|
|
109
|
-
created_at_ms INTEGER NOT NULL,
|
|
110
|
-
PRIMARY KEY (binding_id, app_sequence)
|
|
111
|
-
)`,
|
|
112
|
-
`CREATE TABLE local_filebeat_state (
|
|
113
|
-
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
114
|
-
binary_path TEXT,
|
|
115
|
-
config_dir TEXT,
|
|
116
|
-
desired_version TEXT,
|
|
117
|
-
observed_version TEXT,
|
|
118
|
-
desired_config_hash TEXT,
|
|
119
|
-
observed_config_hash TEXT,
|
|
120
|
-
process_state TEXT,
|
|
121
|
-
last_health_at_ms INTEGER,
|
|
122
|
-
created_at_ms INTEGER NOT NULL,
|
|
123
|
-
updated_at_ms INTEGER NOT NULL
|
|
124
|
-
)`,
|
|
125
|
-
`CREATE TABLE local_activity_bindings (
|
|
126
|
-
id TEXT PRIMARY KEY,
|
|
127
|
-
binding_id TEXT NOT NULL,
|
|
128
|
-
provider TEXT NOT NULL CHECK (provider IN ('claude_code','codex')),
|
|
129
|
-
provider_project_path TEXT NOT NULL,
|
|
130
|
-
path_fingerprint TEXT NOT NULL,
|
|
131
|
-
state TEXT NOT NULL CHECK (state IN ('active','missing','disabled')),
|
|
132
|
-
last_observed_at_ms INTEGER,
|
|
133
|
-
created_at_ms INTEGER NOT NULL,
|
|
134
|
-
updated_at_ms INTEGER NOT NULL
|
|
135
|
-
)`,
|
|
136
|
-
`CREATE UNIQUE INDEX local_activity_bindings_binding_provider_path_unique
|
|
137
|
-
ON local_activity_bindings (binding_id, provider, path_fingerprint)`,
|
|
138
|
-
],
|
|
139
|
-
};
|
|
140
|
-
const MIGRATION_V2 = {
|
|
141
|
-
version: 2,
|
|
142
|
-
statements: [
|
|
143
|
-
`ALTER TABLE local_filebeat_state ADD COLUMN telemetry_enabled INTEGER NOT NULL DEFAULT 1`,
|
|
144
|
-
`ALTER TABLE local_filebeat_state ADD COLUMN last_error_code TEXT`,
|
|
145
|
-
],
|
|
146
|
-
};
|
|
147
|
-
const MIGRATION_V3 = {
|
|
148
|
-
version: 3,
|
|
149
|
-
statements: [
|
|
150
|
-
`CREATE TABLE local_history_decisions (
|
|
151
|
-
id TEXT PRIMARY KEY,
|
|
152
|
-
binding_id TEXT NOT NULL REFERENCES local_bindings(id),
|
|
153
|
-
decision TEXT NOT NULL CHECK (decision IN ('publish_current_state','adopt_without_publishing')),
|
|
154
|
-
old_baseline_commit_id TEXT,
|
|
155
|
-
new_baseline_commit_id TEXT NOT NULL,
|
|
156
|
-
handoff_id TEXT,
|
|
157
|
-
created_at_ms INTEGER NOT NULL
|
|
158
|
-
)`,
|
|
159
|
-
`CREATE INDEX local_history_decisions_binding_idx
|
|
160
|
-
ON local_history_decisions (binding_id, created_at_ms)`,
|
|
161
|
-
`CREATE TABLE local_accepted_uploads (
|
|
162
|
-
binding_id TEXT NOT NULL REFERENCES local_bindings(id),
|
|
163
|
-
commit_id TEXT NOT NULL,
|
|
164
|
-
handoff_id TEXT NOT NULL,
|
|
165
|
-
app_sequence INTEGER NOT NULL,
|
|
166
|
-
manifest_sha256 TEXT NOT NULL,
|
|
167
|
-
created_at_ms INTEGER NOT NULL,
|
|
168
|
-
PRIMARY KEY (binding_id, commit_id)
|
|
169
|
-
)`,
|
|
170
|
-
`CREATE INDEX local_accepted_uploads_handoff_idx
|
|
171
|
-
ON local_accepted_uploads (handoff_id)`,
|
|
172
|
-
],
|
|
173
|
-
};
|
|
174
|
-
const MIGRATION_V4 = {
|
|
175
|
-
version: 4,
|
|
176
|
-
statements: [`ALTER TABLE local_device_state ADD COLUMN active_workspace_id TEXT`],
|
|
177
|
-
};
|
|
178
|
-
const MIGRATION_V5 = {
|
|
179
|
-
version: 5,
|
|
180
|
-
statements: [`ALTER TABLE local_bindings ADD COLUMN telemetry_enabled INTEGER NOT NULL DEFAULT 1`],
|
|
181
|
-
};
|
|
182
|
-
const MIGRATION_V6 = {
|
|
183
|
-
version: 6,
|
|
184
|
-
statements: [
|
|
185
|
-
`ALTER TABLE local_bindings RENAME COLUMN workspace_id TO organization_id`,
|
|
186
|
-
`ALTER TABLE local_device_state RENAME COLUMN active_workspace_id TO active_organization_id`,
|
|
187
|
-
`ALTER TABLE local_bindings RENAME COLUMN app_id TO workspace_id`,
|
|
188
|
-
`ALTER TABLE local_bindings RENAME COLUMN last_applied_app_sequence TO last_applied_workspace_sequence`,
|
|
189
|
-
`ALTER TABLE cached_app_events RENAME COLUMN app_sequence TO workspace_sequence`,
|
|
190
|
-
`ALTER TABLE local_accepted_uploads RENAME COLUMN app_sequence TO workspace_sequence`,
|
|
191
|
-
`ALTER TABLE cached_app_events RENAME TO cached_workspace_events`,
|
|
192
|
-
`DROP INDEX IF EXISTS local_bindings_app_idx`,
|
|
193
|
-
`CREATE INDEX local_bindings_workspace_idx ON local_bindings (workspace_id)`,
|
|
194
|
-
],
|
|
195
|
-
};
|
|
196
|
-
const MIGRATION_V7 = {
|
|
197
|
-
version: 7,
|
|
198
|
-
statements: [
|
|
199
|
-
`ALTER TABLE local_bindings RENAME COLUMN workspace_id TO project_id`,
|
|
200
|
-
`ALTER TABLE local_bindings RENAME COLUMN last_applied_workspace_sequence TO last_applied_project_sequence`,
|
|
201
|
-
`ALTER TABLE cached_workspace_events RENAME COLUMN workspace_sequence TO project_sequence`,
|
|
202
|
-
`ALTER TABLE local_accepted_uploads RENAME COLUMN workspace_sequence TO project_sequence`,
|
|
203
|
-
`ALTER TABLE cached_workspace_events RENAME TO cached_project_events`,
|
|
204
|
-
`DROP INDEX IF EXISTS local_bindings_workspace_idx`,
|
|
205
|
-
`CREATE INDEX local_bindings_project_idx ON local_bindings (project_id)`,
|
|
206
|
-
],
|
|
207
|
-
};
|
|
208
|
-
const MIGRATION_V8 = {
|
|
209
|
-
version: 8,
|
|
210
|
-
statements: [
|
|
211
|
-
`CREATE TABLE record_outbox (
|
|
212
|
-
id TEXT PRIMARY KEY,
|
|
213
|
-
project_id TEXT NOT NULL,
|
|
214
|
-
kind TEXT NOT NULL CHECK (kind IN ('backlog','backlog.remove','bookkeeping','gate_event')),
|
|
215
|
-
idempotency_key TEXT NOT NULL,
|
|
216
|
-
door_url TEXT NOT NULL,
|
|
217
|
-
row_json TEXT NOT NULL CHECK (json_valid(row_json)),
|
|
218
|
-
attempts INTEGER NOT NULL DEFAULT 0,
|
|
219
|
-
next_retry_at_ms INTEGER,
|
|
220
|
-
last_error TEXT,
|
|
221
|
-
created_at_ms INTEGER NOT NULL,
|
|
222
|
-
updated_at_ms INTEGER NOT NULL
|
|
223
|
-
)`,
|
|
224
|
-
`CREATE UNIQUE INDEX record_outbox_idempotency_key_unique ON record_outbox (idempotency_key)`,
|
|
225
|
-
`CREATE TABLE record_store (
|
|
226
|
-
id TEXT PRIMARY KEY,
|
|
227
|
-
project_id TEXT,
|
|
228
|
-
repository_root TEXT NOT NULL,
|
|
229
|
-
kind TEXT NOT NULL CHECK (kind IN ('backlog','backlog.remove','bookkeeping','gate_event')),
|
|
230
|
-
idempotency_key TEXT NOT NULL,
|
|
231
|
-
row_json TEXT NOT NULL CHECK (json_valid(row_json)),
|
|
232
|
-
created_at_ms INTEGER NOT NULL,
|
|
233
|
-
updated_at_ms INTEGER NOT NULL
|
|
234
|
-
)`,
|
|
235
|
-
`CREATE UNIQUE INDEX record_store_idempotency_key_unique ON record_store (idempotency_key)`,
|
|
236
|
-
],
|
|
237
|
-
};
|
|
238
|
-
const V9_LEGACY_BOOKKEEPING_ORIGIN = "'session:legacy-bookkeeping'";
|
|
239
|
-
const V9_BOOKKEEPING_ORIGIN = `(CASE WHEN json_type(row_json, '$.planAnchor') = 'text'
|
|
240
|
-
THEN 'plan:' || json_extract(row_json, '$.planAnchor') ELSE ${V9_LEGACY_BOOKKEEPING_ORIGIN} END)`;
|
|
241
|
-
const V9_NEW_KIND = `(CASE kind WHEN 'backlog.remove' THEN 'backlog' WHEN 'bookkeeping' THEN 'obligation' ELSE kind END)`;
|
|
242
|
-
const V9_NATURAL_KEY = `COALESCE(CASE kind
|
|
243
|
-
WHEN 'bookkeeping' THEN ${V9_BOOKKEEPING_ORIGIN} || '#documentation:' || json_extract(row_json, '$.anchor')
|
|
244
|
-
ELSE json_extract(row_json, '$.anchor') END, '')`;
|
|
245
|
-
const V9_PAYLOAD = `(CASE kind
|
|
246
|
-
WHEN 'backlog' THEN json_remove(row_json, '$.idempotencyKey', '$.planAnchor')
|
|
247
|
-
WHEN 'backlog.remove' THEN json_set(json_remove(row_json, '$.idempotencyKey', '$.planAnchor'), '$.state', 'removed')
|
|
248
|
-
WHEN 'bookkeeping' THEN (CASE WHEN json_type(row_json, '$.text') = 'text'
|
|
249
|
-
THEN json_object('origin', ${V9_BOOKKEEPING_ORIGIN}, 'type', 'documentation',
|
|
250
|
-
'subject', json_extract(row_json, '$.anchor'), 'claim', json_extract(row_json, '$.text'), 'state', 'open')
|
|
251
|
-
ELSE json_object('origin', ${V9_BOOKKEEPING_ORIGIN}, 'type', 'documentation',
|
|
252
|
-
'subject', json_extract(row_json, '$.anchor'), 'state', 'open') END)
|
|
253
|
-
ELSE json_remove(row_json, '$.idempotencyKey') END)`;
|
|
254
|
-
const V9_SOURCE = `(
|
|
255
|
-
SELECT id, project_id, repository_root, NULL AS door_url, 0 AS attempts, NULL AS next_attempt_at,
|
|
256
|
-
NULL AS last_error, created_at_ms, updated_at_ms, json_extract(row_json, '$.planAnchor') AS plan_anchor,
|
|
257
|
-
${V9_NEW_KIND} AS new_kind, ${V9_NATURAL_KEY} AS natural_key, ${V9_PAYLOAD} AS payload_json
|
|
258
|
-
FROM record_store
|
|
259
|
-
UNION ALL
|
|
260
|
-
SELECT id, project_id, NULL AS repository_root, NULLIF(door_url, '') AS door_url, attempts,
|
|
261
|
-
next_retry_at_ms AS next_attempt_at, last_error, created_at_ms, updated_at_ms,
|
|
262
|
-
json_extract(row_json, '$.planAnchor') AS plan_anchor,
|
|
263
|
-
${V9_NEW_KIND} AS new_kind, ${V9_NATURAL_KEY} AS natural_key, ${V9_PAYLOAD} AS payload_json
|
|
264
|
-
FROM record_outbox
|
|
265
|
-
)`;
|
|
266
|
-
const V9_KEY = `COALESCE(project_id, repository_root) || ':' || new_kind || ':' || natural_key`;
|
|
267
|
-
const MIGRATION_V9 = {
|
|
268
|
-
version: 9,
|
|
269
|
-
statements: [
|
|
270
|
-
`CREATE TABLE records (
|
|
271
|
-
id TEXT PRIMARY KEY,
|
|
272
|
-
kind TEXT NOT NULL,
|
|
273
|
-
schema_version INTEGER NOT NULL,
|
|
274
|
-
project_id TEXT,
|
|
275
|
-
session_id TEXT,
|
|
276
|
-
plan_anchor TEXT,
|
|
277
|
-
natural_key TEXT NOT NULL,
|
|
278
|
-
idempotency_key TEXT NOT NULL,
|
|
279
|
-
payload_json TEXT NOT NULL CHECK (json_valid(payload_json)),
|
|
280
|
-
sync_state TEXT NOT NULL,
|
|
281
|
-
attempts INTEGER NOT NULL DEFAULT 0,
|
|
282
|
-
next_attempt_at INTEGER,
|
|
283
|
-
last_error TEXT,
|
|
284
|
-
created_at INTEGER NOT NULL,
|
|
285
|
-
repository_root TEXT,
|
|
286
|
-
door_url TEXT,
|
|
287
|
-
placement TEXT,
|
|
288
|
-
credential TEXT
|
|
289
|
-
)`,
|
|
290
|
-
`CREATE UNIQUE INDEX records_idempotency_key_unique ON records (idempotency_key)`,
|
|
291
|
-
`CREATE INDEX records_sync_state_next_attempt_idx ON records (sync_state, next_attempt_at)`,
|
|
292
|
-
`INSERT OR REPLACE INTO records (id, kind, schema_version, project_id, session_id, plan_anchor, natural_key,
|
|
293
|
-
idempotency_key, payload_json, sync_state, attempts, next_attempt_at, last_error, created_at,
|
|
294
|
-
repository_root, door_url, placement, credential)
|
|
295
|
-
SELECT id, new_kind, 1, project_id, NULL, plan_anchor, natural_key, ${V9_KEY}, payload_json, 'pending',
|
|
296
|
-
attempts, next_attempt_at, last_error, created_at_ms, repository_root, door_url, NULL, NULL
|
|
297
|
-
FROM ${V9_SOURCE}
|
|
298
|
-
ORDER BY created_at_ms, updated_at_ms`,
|
|
299
|
-
`CREATE TEMP TABLE records_v9_guard (shortfall INTEGER NOT NULL CONSTRAINT records_v9_guard CHECK (shortfall = 0))`,
|
|
300
|
-
`INSERT INTO records_v9_guard SELECT count(*) FROM records
|
|
301
|
-
WHERE kind = 'backlog' AND json_extract(payload_json, '$.anchor') IS NULL`,
|
|
302
|
-
`INSERT INTO records_v9_guard SELECT count(*) FROM records
|
|
303
|
-
WHERE kind = 'obligation' AND (json_extract(payload_json, '$.origin') IS NULL
|
|
304
|
-
OR json_extract(payload_json, '$.subject') IS NULL OR json_extract(payload_json, '$.state') IS NULL)`,
|
|
305
|
-
`INSERT INTO records_v9_guard SELECT
|
|
306
|
-
(SELECT count(DISTINCT ${V9_KEY}) FROM ${V9_SOURCE}) - (SELECT count(*) FROM records)`,
|
|
307
|
-
`DROP TABLE records_v9_guard`,
|
|
308
|
-
`DROP TABLE record_outbox`,
|
|
309
|
-
`DROP TABLE record_store`,
|
|
310
|
-
],
|
|
311
|
-
};
|
|
312
|
-
const MIGRATION_V10 = {
|
|
313
|
-
version: 10,
|
|
314
|
-
statements: [
|
|
315
|
-
`CREATE TABLE routing_latch (
|
|
316
|
-
session_id TEXT,
|
|
317
|
-
job_kind TEXT,
|
|
318
|
-
failures INTEGER NOT NULL DEFAULT 0,
|
|
319
|
-
budget_misses INTEGER NOT NULL DEFAULT 0,
|
|
320
|
-
latched_at INTEGER,
|
|
321
|
-
PRIMARY KEY (session_id, job_kind)
|
|
322
|
-
)`,
|
|
323
|
-
`CREATE TABLE label_rows (
|
|
324
|
-
id TEXT PRIMARY KEY,
|
|
325
|
-
session_id TEXT,
|
|
326
|
-
agent_type TEXT,
|
|
327
|
-
path TEXT,
|
|
328
|
-
estimated_tokens INTEGER,
|
|
329
|
-
returned_tokens INTEGER,
|
|
330
|
-
handler TEXT,
|
|
331
|
-
check_result TEXT,
|
|
332
|
-
bypass_used INTEGER,
|
|
333
|
-
latency_ms INTEGER,
|
|
334
|
-
tokens_kept_out INTEGER,
|
|
335
|
-
created_at INTEGER,
|
|
336
|
-
sync_state TEXT NOT NULL DEFAULT 'pending'
|
|
337
|
-
)`,
|
|
338
|
-
`CREATE INDEX label_rows_sync_state_created_at_idx ON label_rows (sync_state, created_at)`,
|
|
339
|
-
`CREATE TABLE gated_paths (
|
|
340
|
-
session_id TEXT,
|
|
341
|
-
path TEXT,
|
|
342
|
-
gated_at INTEGER,
|
|
343
|
-
PRIMARY KEY (session_id, path)
|
|
344
|
-
)`,
|
|
345
|
-
],
|
|
346
|
-
};
|
|
347
|
-
export const MIGRATIONS = [
|
|
348
|
-
MIGRATION_V1,
|
|
349
|
-
MIGRATION_V2,
|
|
350
|
-
MIGRATION_V3,
|
|
351
|
-
MIGRATION_V4,
|
|
352
|
-
MIGRATION_V5,
|
|
353
|
-
MIGRATION_V6,
|
|
354
|
-
MIGRATION_V7,
|
|
355
|
-
MIGRATION_V8,
|
|
356
|
-
MIGRATION_V9,
|
|
357
|
-
MIGRATION_V10,
|
|
358
|
-
];
|
|
359
|
-
export const LATEST_SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
|
|
360
|
-
export class LocalSchemaTooNewError extends Error {
|
|
361
|
-
foundVersion;
|
|
362
|
-
supportedVersion;
|
|
363
|
-
constructor(foundVersion, supportedVersion) {
|
|
364
|
-
super(`This machine's NuStack local store is at schema version ${foundVersion}, but this CLI only supports up to ${supportedVersion}. Update the nustack CLI to continue.`);
|
|
365
|
-
this.name = "LocalSchemaTooNewError";
|
|
366
|
-
this.foundVersion = foundVersion;
|
|
367
|
-
this.supportedVersion = supportedVersion;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
function readUserVersion(db) {
|
|
371
|
-
const row = db.prepare("PRAGMA user_version").get();
|
|
372
|
-
return typeof row?.user_version === "number" ? row.user_version : 0;
|
|
373
|
-
}
|
|
374
|
-
export function migrateToLatest(db) {
|
|
375
|
-
const current = readUserVersion(db);
|
|
376
|
-
if (current > LATEST_SCHEMA_VERSION) {
|
|
377
|
-
throw new LocalSchemaTooNewError(current, LATEST_SCHEMA_VERSION);
|
|
378
|
-
}
|
|
379
|
-
for (const migration of MIGRATIONS) {
|
|
380
|
-
if (migration.version <= current)
|
|
381
|
-
continue;
|
|
382
|
-
db.exec("BEGIN IMMEDIATE");
|
|
383
|
-
if (readUserVersion(db) >= migration.version) {
|
|
384
|
-
db.exec("COMMIT");
|
|
385
|
-
continue;
|
|
386
|
-
}
|
|
387
|
-
try {
|
|
388
|
-
for (const statement of migration.statements) {
|
|
389
|
-
db.exec(statement);
|
|
390
|
-
}
|
|
391
|
-
db.exec(`PRAGMA user_version = ${migration.version}`);
|
|
392
|
-
db.exec("COMMIT");
|
|
393
|
-
}
|
|
394
|
-
catch (error) {
|
|
395
|
-
db.exec("ROLLBACK");
|
|
396
|
-
throw error;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
package/dist/lib/records/door.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { assertCredentialOrigin, resolveNuStackOrigin } from "../nustack_client.js";
|
|
2
|
-
import { resolveProjectId } from "../project_context.js";
|
|
3
|
-
import { readProjectContract } from "../project_contract.js";
|
|
4
|
-
import { originOf } from "../origin_guard.js";
|
|
5
|
-
export const RECORD_KINDS = ["obligation", "gate_event", "changelog", "memory", "backlog", "proposal", "triage"];
|
|
6
|
-
export const LEGACY_KIND_TRANSLATION = { "backlog.remove": "backlog", bookkeeping: "obligation" };
|
|
7
|
-
export const RECORDS_BATCH_CAP = 200;
|
|
8
|
-
export const RECORD_SCHEMA_VERSION = 1;
|
|
9
|
-
const REQUEST_TIMEOUT_MS = 10_000;
|
|
10
|
-
const RETRY_BASE_MS = 30_000;
|
|
11
|
-
const RETRY_CEILING_MS = 60 * 60 * 1000;
|
|
12
|
-
export function isRecordKind(value) {
|
|
13
|
-
return typeof value === "string" && RECORD_KINDS.includes(value);
|
|
14
|
-
}
|
|
15
|
-
export function isLegacyKind(value) {
|
|
16
|
-
return typeof value === "string" && Object.hasOwn(LEGACY_KIND_TRANSLATION, value);
|
|
17
|
-
}
|
|
18
|
-
export function isEmitKind(value) {
|
|
19
|
-
return isRecordKind(value) || isLegacyKind(value);
|
|
20
|
-
}
|
|
21
|
-
export function translatedKindOf(kind) {
|
|
22
|
-
return isLegacyKind(kind) ? LEGACY_KIND_TRANSLATION[kind] : kind;
|
|
23
|
-
}
|
|
24
|
-
export async function readMountDoor(root) {
|
|
25
|
-
return (await readProjectContract(root)).door;
|
|
26
|
-
}
|
|
27
|
-
export async function resolveDoor(root, globals, projectFlag) {
|
|
28
|
-
const project = await resolveProjectId(root, projectFlag ? { project: projectFlag } : {});
|
|
29
|
-
if (!project.ok)
|
|
30
|
-
return { kind: "store", projectId: null, root, reason: "this folder is not bound to a Project" };
|
|
31
|
-
const contract = await readProjectContract(root);
|
|
32
|
-
const door = contract.door;
|
|
33
|
-
if (door === null)
|
|
34
|
-
return { kind: "store", projectId: project.projectId, root, reason: "the harness manifest configures no door" };
|
|
35
|
-
let origin;
|
|
36
|
-
try {
|
|
37
|
-
origin = resolveNuStackOrigin(globals, root).url;
|
|
38
|
-
}
|
|
39
|
-
catch (error) {
|
|
40
|
-
return { kind: "store", projectId: project.projectId, root, reason: error instanceof Error ? error.message : String(error) };
|
|
41
|
-
}
|
|
42
|
-
if (door !== undefined && contract.doorSource === "contract" && originOf(door) !== originOf(origin)) {
|
|
43
|
-
const reason = `the contract's door ${door} is not on the resolved origin ${origin}; records stay local`;
|
|
44
|
-
process.stderr.write(`nustack: ${reason}\n`);
|
|
45
|
-
return { kind: "store", projectId: project.projectId, root, reason };
|
|
46
|
-
}
|
|
47
|
-
return { kind: "door", projectId: project.projectId, doorUrl: door ?? origin, isPlatformDefault: door === undefined, root };
|
|
48
|
-
}
|
|
49
|
-
export function retryDelayMs(attempts) {
|
|
50
|
-
return Math.min(RETRY_BASE_MS * 2 ** Math.max(0, attempts - 1), RETRY_CEILING_MS);
|
|
51
|
-
}
|
|
52
|
-
export async function postRecords(doorUrl, projectId, request, bearer) {
|
|
53
|
-
try {
|
|
54
|
-
await assertCredentialOrigin(doorUrl);
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
const code = typeof error.code === "string" ? error.code : null;
|
|
58
|
-
return { ok: false, status: 0, code, message: error instanceof Error ? error.message : String(error) };
|
|
59
|
-
}
|
|
60
|
-
let response;
|
|
61
|
-
try {
|
|
62
|
-
response = await fetch(`${doorUrl}/api/v2/projects/${encodeURIComponent(projectId)}/records`, {
|
|
63
|
-
method: "POST",
|
|
64
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${bearer}` },
|
|
65
|
-
body: JSON.stringify(request),
|
|
66
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
catch (error) {
|
|
70
|
-
return { ok: false, status: 0, code: null, message: `the door did not answer: ${error instanceof Error ? error.message : String(error)}` };
|
|
71
|
-
}
|
|
72
|
-
const body = (await response.json().catch(() => null));
|
|
73
|
-
if (!response.ok) {
|
|
74
|
-
const code = typeof body?.error?.code === "string" ? body.error.code : null;
|
|
75
|
-
const detail = typeof body?.error?.message === "string" ? body.error.message : `HTTP ${response.status}`;
|
|
76
|
-
return { ok: false, status: response.status, code, message: `${code ?? `HTTP ${response.status}`}: ${detail}` };
|
|
77
|
-
}
|
|
78
|
-
return { ok: true, landed: body?.data?.landed ?? 0, skipped: body?.data?.skipped ?? 0 };
|
|
79
|
-
}
|