@nurix/nustack 0.10.0-dev.10
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 +77 -0
- package/dist/commands/add.js +130 -0
- package/dist/commands/agent.js +100 -0
- package/dist/commands/bootstrap.js +54 -0
- package/dist/commands/describe.js +34 -0
- package/dist/commands/device.js +159 -0
- package/dist/commands/docs.js +119 -0
- package/dist/commands/doctor.js +284 -0
- package/dist/commands/graph.js +34 -0
- package/dist/commands/handoff.js +108 -0
- package/dist/commands/init.js +280 -0
- package/dist/commands/lane.js +12 -0
- package/dist/commands/list.js +20 -0
- package/dist/commands/login.js +134 -0
- package/dist/commands/logout.js +44 -0
- package/dist/commands/open.js +54 -0
- package/dist/commands/organization.js +114 -0
- package/dist/commands/review.js +152 -0
- package/dist/commands/search.js +57 -0
- package/dist/commands/sessions.js +62 -0
- package/dist/commands/sync.js +85 -0
- package/dist/commands/telemetry.js +112 -0
- package/dist/commands/toolkit.js +167 -0
- package/dist/commands/validate.js +67 -0
- package/dist/commands/workspace-sync.js +645 -0
- package/dist/commands/workspace.js +479 -0
- package/dist/index.js +357 -0
- package/dist/lib/agent/claudeHooks.js +77 -0
- package/dist/lib/auth_grant.js +398 -0
- package/dist/lib/auth_recovery.js +49 -0
- package/dist/lib/browser_opener.js +24 -0
- package/dist/lib/device_identity.js +28 -0
- package/dist/lib/device_keychain.js +101 -0
- package/dist/lib/env_file.js +75 -0
- package/dist/lib/etna/manager.js +43 -0
- package/dist/lib/etna/profile.js +41 -0
- package/dist/lib/etna/reconcile.js +77 -0
- package/dist/lib/filebeat_installer.js +246 -0
- package/dist/lib/git-sync/core/commit_range.js +37 -0
- package/dist/lib/git-sync/core/engine.js +263 -0
- package/dist/lib/git-sync/core/git_runner.js +137 -0
- package/dist/lib/git-sync/core/operation_state.js +22 -0
- package/dist/lib/git-sync/core/path_normalizer.js +98 -0
- package/dist/lib/git-sync/core/reconciliation_lock.js +103 -0
- package/dist/lib/git-sync/core/repository.js +49 -0
- package/dist/lib/git-sync/core/watch_stream.js +86 -0
- package/dist/lib/git-sync/core/watcher.js +61 -0
- package/dist/lib/git-sync/inbound/applier.js +131 -0
- package/dist/lib/git-sync/inbound/apply_commit.js +225 -0
- package/dist/lib/git-sync/inbound/approval.js +117 -0
- package/dist/lib/git-sync/inbound/content_decision.js +148 -0
- package/dist/lib/git-sync/inbound/download.js +223 -0
- package/dist/lib/git-sync/inbound/event_sync.js +133 -0
- package/dist/lib/git-sync/inbound/index.js +10 -0
- package/dist/lib/git-sync/inbound/notification.js +39 -0
- package/dist/lib/git-sync/inbound/state_fingerprint.js +32 -0
- package/dist/lib/git-sync/inbound/technical_wait.js +39 -0
- package/dist/lib/git-sync/inbound/undo.js +198 -0
- package/dist/lib/git-sync/outbound/extraction.js +107 -0
- package/dist/lib/git-sync/outbound/history_decision.js +112 -0
- package/dist/lib/git-sync/outbound/index.js +26 -0
- package/dist/lib/git-sync/outbound/manifest_builder.js +58 -0
- package/dist/lib/git-sync/outbound/publisher.js +51 -0
- package/dist/lib/git-sync/outbound/queue.js +123 -0
- package/dist/lib/git-sync/outbound/state.js +12 -0
- package/dist/lib/git-sync/outbound/upload_client.js +141 -0
- package/dist/lib/git-sync/recovery/bundle.js +56 -0
- package/dist/lib/git-sync/recovery/crypto.js +114 -0
- package/dist/lib/git-sync/recovery/index.js +3 -0
- package/dist/lib/git-sync/recovery/store.js +55 -0
- package/dist/lib/git-sync/whitelist/index.js +4 -0
- package/dist/lib/git-sync/whitelist/matcher.js +62 -0
- package/dist/lib/git-sync/whitelist/store.js +99 -0
- package/dist/lib/git-sync/whitelist/types.js +46 -0
- package/dist/lib/git-sync/whitelist/validation.js +60 -0
- package/dist/lib/graph/answer.js +171 -0
- package/dist/lib/graph/freshness.js +29 -0
- package/dist/lib/graph/library.js +19 -0
- package/dist/lib/guards.js +106 -0
- package/dist/lib/interactive.js +12 -0
- package/dist/lib/json_output.js +8 -0
- package/dist/lib/lanes/claudeAdapter.js +135 -0
- package/dist/lib/lanes/frames.js +73 -0
- package/dist/lib/lanes/supervisor.js +247 -0
- package/dist/lib/lanes/worktree.js +33 -0
- package/dist/lib/local_store/accepted_uploads.js +49 -0
- package/dist/lib/local_store/bindings.js +163 -0
- package/dist/lib/local_store/commit_origins.js +25 -0
- package/dist/lib/local_store/database.js +71 -0
- package/dist/lib/local_store/device_state.js +72 -0
- package/dist/lib/local_store/filebeat_state.js +133 -0
- package/dist/lib/local_store/handoff_applications.js +166 -0
- package/dist/lib/local_store/history_decisions.js +48 -0
- package/dist/lib/local_store/index.js +14 -0
- package/dist/lib/local_store/migrations.js +240 -0
- package/dist/lib/local_store/paths.js +24 -0
- package/dist/lib/local_store/pending_uploads.js +76 -0
- package/dist/lib/local_store/recovery_bundles.js +66 -0
- package/dist/lib/local_store/sync_vocabulary.js +33 -0
- package/dist/lib/local_store/workspace_events_cache.js +54 -0
- package/dist/lib/loopback.js +78 -0
- package/dist/lib/manifest.js +83 -0
- package/dist/lib/nustack_client.js +547 -0
- package/dist/lib/nustack_store.js +17 -0
- package/dist/lib/onboarding/attach_existing_git.js +65 -0
- package/dist/lib/onboarding/binding.js +91 -0
- package/dist/lib/onboarding/folder_state.js +99 -0
- package/dist/lib/onboarding/git_init.js +10 -0
- package/dist/lib/onboarding/handoff_apply.js +68 -0
- package/dist/lib/onboarding/init_fresh_folder.js +100 -0
- package/dist/lib/onboarding/manifest_writer.js +161 -0
- package/dist/lib/onboarding/web_first_attach.js +133 -0
- package/dist/lib/package_manager.js +23 -0
- package/dist/lib/review/client.js +66 -0
- package/dist/lib/schema_validator.js +24 -0
- package/dist/lib/secure_file.js +10 -0
- package/dist/lib/session_telemetry.js +263 -0
- package/dist/lib/sessions/client.js +48 -0
- package/dist/lib/sessions/outsideSessions.js +54 -0
- package/dist/lib/sse_framing.js +48 -0
- package/dist/lib/telemetry/config_renderer.js +175 -0
- package/dist/lib/telemetry/health_report.js +173 -0
- package/dist/lib/telemetry/provider_paths.js +38 -0
- package/dist/lib/telemetry/redaction_policy.js +90 -0
- package/dist/lib/telemetry/reload.js +84 -0
- package/dist/lib/workspace_context.js +121 -0
- package/dist/spec_schema.json +422 -0
- package/dist/wire/auth.js +1 -0
- package/dist/wire/sync-paths.js +25 -0
- package/dist/wire/telemetry.js +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { withTransaction } from "./database.js";
|
|
2
|
+
function mapFilebeat(row) {
|
|
3
|
+
return {
|
|
4
|
+
binaryPath: row.binary_path ?? null,
|
|
5
|
+
configDir: row.config_dir ?? null,
|
|
6
|
+
desiredVersion: row.desired_version ?? null,
|
|
7
|
+
observedVersion: row.observed_version ?? null,
|
|
8
|
+
desiredConfigHash: row.desired_config_hash ?? null,
|
|
9
|
+
observedConfigHash: row.observed_config_hash ?? null,
|
|
10
|
+
processState: row.process_state ?? null,
|
|
11
|
+
telemetryEnabled: (row.telemetry_enabled ?? 1) !== 0,
|
|
12
|
+
lastErrorCode: row.last_error_code ?? null,
|
|
13
|
+
lastHealthAtMs: row.last_health_at_ms ?? null,
|
|
14
|
+
createdAtMs: row.created_at_ms,
|
|
15
|
+
updatedAtMs: row.updated_at_ms,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function readFilebeatState(store) {
|
|
19
|
+
const row = store.db.prepare("SELECT * FROM local_filebeat_state WHERE id = 1").get();
|
|
20
|
+
return row ? mapFilebeat(row) : null;
|
|
21
|
+
}
|
|
22
|
+
export function upsertFilebeatState(store, patch) {
|
|
23
|
+
return withTransaction(store, () => {
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
const current = readFilebeatState(store);
|
|
26
|
+
const pick = (key, fallback) => Object.prototype.hasOwnProperty.call(patch, key) ? (patch[key] ?? null) : fallback;
|
|
27
|
+
const telemetryEnabled = Object.prototype.hasOwnProperty.call(patch, "telemetryEnabled")
|
|
28
|
+
? patch.telemetryEnabled
|
|
29
|
+
? 1
|
|
30
|
+
: 0
|
|
31
|
+
: current?.telemetryEnabled === false
|
|
32
|
+
? 0
|
|
33
|
+
: 1;
|
|
34
|
+
store.db
|
|
35
|
+
.prepare(`INSERT INTO local_filebeat_state (
|
|
36
|
+
id, binary_path, config_dir, desired_version, observed_version,
|
|
37
|
+
desired_config_hash, observed_config_hash, process_state,
|
|
38
|
+
telemetry_enabled, last_error_code, last_health_at_ms,
|
|
39
|
+
created_at_ms, updated_at_ms
|
|
40
|
+
) VALUES (
|
|
41
|
+
1, :binary_path, :config_dir, :desired_version, :observed_version,
|
|
42
|
+
:desired_config_hash, :observed_config_hash, :process_state,
|
|
43
|
+
:telemetry_enabled, :last_error_code, :last_health_at_ms,
|
|
44
|
+
:created_at_ms, :updated_at_ms
|
|
45
|
+
)
|
|
46
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
47
|
+
binary_path = excluded.binary_path,
|
|
48
|
+
config_dir = excluded.config_dir,
|
|
49
|
+
desired_version = excluded.desired_version,
|
|
50
|
+
observed_version = excluded.observed_version,
|
|
51
|
+
desired_config_hash = excluded.desired_config_hash,
|
|
52
|
+
observed_config_hash = excluded.observed_config_hash,
|
|
53
|
+
process_state = excluded.process_state,
|
|
54
|
+
telemetry_enabled = excluded.telemetry_enabled,
|
|
55
|
+
last_error_code = excluded.last_error_code,
|
|
56
|
+
last_health_at_ms = excluded.last_health_at_ms,
|
|
57
|
+
updated_at_ms = excluded.updated_at_ms`)
|
|
58
|
+
.run({
|
|
59
|
+
binary_path: pick("binaryPath", current?.binaryPath ?? null),
|
|
60
|
+
config_dir: pick("configDir", current?.configDir ?? null),
|
|
61
|
+
desired_version: pick("desiredVersion", current?.desiredVersion ?? null),
|
|
62
|
+
observed_version: pick("observedVersion", current?.observedVersion ?? null),
|
|
63
|
+
desired_config_hash: pick("desiredConfigHash", current?.desiredConfigHash ?? null),
|
|
64
|
+
observed_config_hash: pick("observedConfigHash", current?.observedConfigHash ?? null),
|
|
65
|
+
process_state: pick("processState", current?.processState ?? null),
|
|
66
|
+
telemetry_enabled: telemetryEnabled,
|
|
67
|
+
last_error_code: pick("lastErrorCode", current?.lastErrorCode ?? null),
|
|
68
|
+
last_health_at_ms: pick("lastHealthAtMs", current?.lastHealthAtMs ?? null),
|
|
69
|
+
created_at_ms: current?.createdAtMs ?? now,
|
|
70
|
+
updated_at_ms: now,
|
|
71
|
+
});
|
|
72
|
+
return readFilebeatState(store);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function mapActivity(row) {
|
|
76
|
+
return {
|
|
77
|
+
id: row.id,
|
|
78
|
+
bindingId: row.binding_id,
|
|
79
|
+
provider: row.provider,
|
|
80
|
+
providerProjectPath: row.provider_project_path,
|
|
81
|
+
pathFingerprint: row.path_fingerprint,
|
|
82
|
+
state: row.state,
|
|
83
|
+
lastObservedAtMs: row.last_observed_at_ms ?? null,
|
|
84
|
+
createdAtMs: row.created_at_ms,
|
|
85
|
+
updatedAtMs: row.updated_at_ms,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export function upsertActivityBinding(store, input) {
|
|
89
|
+
const now = Date.now();
|
|
90
|
+
store.db
|
|
91
|
+
.prepare(`INSERT INTO local_activity_bindings (
|
|
92
|
+
id, binding_id, provider, provider_project_path, path_fingerprint,
|
|
93
|
+
state, last_observed_at_ms, created_at_ms, updated_at_ms
|
|
94
|
+
) VALUES (
|
|
95
|
+
:id, :binding_id, :provider, :provider_project_path, :path_fingerprint,
|
|
96
|
+
:state, :last_observed_at_ms, :created_at_ms, :updated_at_ms
|
|
97
|
+
)
|
|
98
|
+
ON CONFLICT(binding_id, provider, path_fingerprint) DO UPDATE SET
|
|
99
|
+
provider_project_path = excluded.provider_project_path,
|
|
100
|
+
state = excluded.state,
|
|
101
|
+
last_observed_at_ms = excluded.last_observed_at_ms,
|
|
102
|
+
updated_at_ms = excluded.updated_at_ms`)
|
|
103
|
+
.run({
|
|
104
|
+
id: input.id,
|
|
105
|
+
binding_id: input.bindingId,
|
|
106
|
+
provider: input.provider,
|
|
107
|
+
provider_project_path: input.providerProjectPath,
|
|
108
|
+
path_fingerprint: input.pathFingerprint,
|
|
109
|
+
state: input.state ?? "active",
|
|
110
|
+
last_observed_at_ms: input.lastObservedAtMs ?? null,
|
|
111
|
+
created_at_ms: now,
|
|
112
|
+
updated_at_ms: now,
|
|
113
|
+
});
|
|
114
|
+
const row = store.db
|
|
115
|
+
.prepare(`SELECT * FROM local_activity_bindings
|
|
116
|
+
WHERE binding_id = :binding_id AND provider = :provider AND path_fingerprint = :path_fingerprint`)
|
|
117
|
+
.get({
|
|
118
|
+
binding_id: input.bindingId,
|
|
119
|
+
provider: input.provider,
|
|
120
|
+
path_fingerprint: input.pathFingerprint,
|
|
121
|
+
});
|
|
122
|
+
return mapActivity(row);
|
|
123
|
+
}
|
|
124
|
+
export function listActivityBindings(store, bindingId) {
|
|
125
|
+
const rows = (bindingId === undefined
|
|
126
|
+
? store.db
|
|
127
|
+
.prepare("SELECT * FROM local_activity_bindings ORDER BY created_at_ms ASC")
|
|
128
|
+
.all()
|
|
129
|
+
: store.db
|
|
130
|
+
.prepare("SELECT * FROM local_activity_bindings WHERE binding_id = :binding_id ORDER BY created_at_ms ASC")
|
|
131
|
+
.all({ binding_id: bindingId }));
|
|
132
|
+
return rows.map(mapActivity);
|
|
133
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { withTransaction } from "./database.js";
|
|
2
|
+
import { isLegalApplicationTransition } from "./sync_vocabulary.js";
|
|
3
|
+
export class IllegalApplicationTransitionError extends Error {
|
|
4
|
+
from;
|
|
5
|
+
to;
|
|
6
|
+
constructor(from, to) {
|
|
7
|
+
super(`Illegal inbound-application transition: ${from} → ${to}`);
|
|
8
|
+
this.name = "IllegalApplicationTransitionError";
|
|
9
|
+
this.from = from;
|
|
10
|
+
this.to = to;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function mapRow(row) {
|
|
14
|
+
return {
|
|
15
|
+
handoffId: row.handoff_id,
|
|
16
|
+
bindingId: row.binding_id,
|
|
17
|
+
state: row.state,
|
|
18
|
+
manifestSha256: row.manifest_sha256 ?? null,
|
|
19
|
+
baseManifestJson: row.base_manifest_json ?? null,
|
|
20
|
+
targetManifestJson: row.target_manifest_json ?? null,
|
|
21
|
+
localStateFingerprint: row.local_state_fingerprint ?? null,
|
|
22
|
+
technicalWaitReason: row.technical_wait_reason ?? null,
|
|
23
|
+
decisionState: row.decision_state ?? null,
|
|
24
|
+
approvedByUserId: row.approved_by_user_id ?? null,
|
|
25
|
+
generatedCommitId: row.generated_commit_id ?? null,
|
|
26
|
+
notificationState: row.notification_state ?? null,
|
|
27
|
+
recoveryBundleId: row.recovery_bundle_id ?? null,
|
|
28
|
+
revertCommitId: row.revert_commit_id ?? null,
|
|
29
|
+
supersededByHandoffId: row.superseded_by_handoff_id ?? null,
|
|
30
|
+
errorCode: row.error_code ?? null,
|
|
31
|
+
errorDetail: row.error_detail ?? null,
|
|
32
|
+
createdAtMs: row.created_at_ms,
|
|
33
|
+
updatedAtMs: row.updated_at_ms,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function getApplication(store, handoffId, bindingId) {
|
|
37
|
+
const row = store.db
|
|
38
|
+
.prepare(`SELECT * FROM inbound_handoff_applications
|
|
39
|
+
WHERE handoff_id = :handoff_id AND binding_id = :binding_id`)
|
|
40
|
+
.get({ handoff_id: handoffId, binding_id: bindingId });
|
|
41
|
+
return row ? mapRow(row) : null;
|
|
42
|
+
}
|
|
43
|
+
export function listApplicationsForBinding(store, bindingId, states) {
|
|
44
|
+
if (!states || states.length === 0) {
|
|
45
|
+
const rows = store.db
|
|
46
|
+
.prepare(`SELECT * FROM inbound_handoff_applications
|
|
47
|
+
WHERE binding_id = :binding_id ORDER BY created_at_ms ASC`)
|
|
48
|
+
.all({ binding_id: bindingId });
|
|
49
|
+
return rows.map(mapRow);
|
|
50
|
+
}
|
|
51
|
+
const placeholders = states.map((_, i) => `:st${i}`).join(", ");
|
|
52
|
+
const params = { binding_id: bindingId };
|
|
53
|
+
states.forEach((s, i) => {
|
|
54
|
+
params[`st${i}`] = s;
|
|
55
|
+
});
|
|
56
|
+
const rows = store.db
|
|
57
|
+
.prepare(`SELECT * FROM inbound_handoff_applications
|
|
58
|
+
WHERE binding_id = :binding_id AND state IN (${placeholders})
|
|
59
|
+
ORDER BY created_at_ms ASC`)
|
|
60
|
+
.all(params);
|
|
61
|
+
return rows.map(mapRow);
|
|
62
|
+
}
|
|
63
|
+
export function upsertApplication(store, input) {
|
|
64
|
+
return withTransaction(store, () => {
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
const existing = getApplication(store, input.handoffId, input.bindingId);
|
|
67
|
+
if (!existing) {
|
|
68
|
+
store.db
|
|
69
|
+
.prepare(`INSERT INTO inbound_handoff_applications (
|
|
70
|
+
handoff_id, binding_id, state, manifest_sha256, base_manifest_json,
|
|
71
|
+
target_manifest_json, local_state_fingerprint, technical_wait_reason,
|
|
72
|
+
decision_state, approved_by_user_id, generated_commit_id,
|
|
73
|
+
notification_state, recovery_bundle_id, revert_commit_id,
|
|
74
|
+
superseded_by_handoff_id, error_code, error_detail,
|
|
75
|
+
created_at_ms, updated_at_ms
|
|
76
|
+
) VALUES (
|
|
77
|
+
:handoff_id, :binding_id, :state, :manifest_sha256, :base_manifest_json,
|
|
78
|
+
:target_manifest_json, :local_state_fingerprint, :technical_wait_reason,
|
|
79
|
+
:decision_state, :approved_by_user_id, :generated_commit_id,
|
|
80
|
+
:notification_state, :recovery_bundle_id, :revert_commit_id,
|
|
81
|
+
:superseded_by_handoff_id, :error_code, :error_detail,
|
|
82
|
+
:created_at_ms, :updated_at_ms
|
|
83
|
+
)`)
|
|
84
|
+
.run({
|
|
85
|
+
handoff_id: input.handoffId,
|
|
86
|
+
binding_id: input.bindingId,
|
|
87
|
+
state: input.state ?? "received",
|
|
88
|
+
manifest_sha256: input.manifestSha256 ?? null,
|
|
89
|
+
base_manifest_json: input.baseManifestJson ?? null,
|
|
90
|
+
target_manifest_json: input.targetManifestJson ?? null,
|
|
91
|
+
local_state_fingerprint: input.localStateFingerprint ?? null,
|
|
92
|
+
technical_wait_reason: input.technicalWaitReason ?? null,
|
|
93
|
+
decision_state: input.decisionState ?? null,
|
|
94
|
+
approved_by_user_id: input.approvedByUserId ?? null,
|
|
95
|
+
generated_commit_id: input.generatedCommitId ?? null,
|
|
96
|
+
notification_state: input.notificationState ?? null,
|
|
97
|
+
recovery_bundle_id: input.recoveryBundleId ?? null,
|
|
98
|
+
revert_commit_id: input.revertCommitId ?? null,
|
|
99
|
+
superseded_by_handoff_id: input.supersededByHandoffId ?? null,
|
|
100
|
+
error_code: input.errorCode ?? null,
|
|
101
|
+
error_detail: input.errorDetail ?? null,
|
|
102
|
+
created_at_ms: now,
|
|
103
|
+
updated_at_ms: now,
|
|
104
|
+
});
|
|
105
|
+
return getApplication(store, input.handoffId, input.bindingId);
|
|
106
|
+
}
|
|
107
|
+
const pick = (key, fallback) => Object.prototype.hasOwnProperty.call(input, key)
|
|
108
|
+
? (input[key] ?? null)
|
|
109
|
+
: fallback;
|
|
110
|
+
store.db
|
|
111
|
+
.prepare(`UPDATE inbound_handoff_applications SET
|
|
112
|
+
manifest_sha256 = :manifest_sha256,
|
|
113
|
+
base_manifest_json = :base_manifest_json,
|
|
114
|
+
target_manifest_json = :target_manifest_json,
|
|
115
|
+
local_state_fingerprint = :local_state_fingerprint,
|
|
116
|
+
technical_wait_reason = :technical_wait_reason,
|
|
117
|
+
decision_state = :decision_state,
|
|
118
|
+
approved_by_user_id = :approved_by_user_id,
|
|
119
|
+
generated_commit_id = :generated_commit_id,
|
|
120
|
+
notification_state = :notification_state,
|
|
121
|
+
recovery_bundle_id = :recovery_bundle_id,
|
|
122
|
+
revert_commit_id = :revert_commit_id,
|
|
123
|
+
superseded_by_handoff_id = :superseded_by_handoff_id,
|
|
124
|
+
error_code = :error_code,
|
|
125
|
+
error_detail = :error_detail,
|
|
126
|
+
updated_at_ms = :updated_at_ms
|
|
127
|
+
WHERE handoff_id = :handoff_id AND binding_id = :binding_id`)
|
|
128
|
+
.run({
|
|
129
|
+
manifest_sha256: pick("manifestSha256", existing.manifestSha256),
|
|
130
|
+
base_manifest_json: pick("baseManifestJson", existing.baseManifestJson),
|
|
131
|
+
target_manifest_json: pick("targetManifestJson", existing.targetManifestJson),
|
|
132
|
+
local_state_fingerprint: pick("localStateFingerprint", existing.localStateFingerprint),
|
|
133
|
+
technical_wait_reason: pick("technicalWaitReason", existing.technicalWaitReason),
|
|
134
|
+
decision_state: pick("decisionState", existing.decisionState),
|
|
135
|
+
approved_by_user_id: pick("approvedByUserId", existing.approvedByUserId),
|
|
136
|
+
generated_commit_id: pick("generatedCommitId", existing.generatedCommitId),
|
|
137
|
+
notification_state: pick("notificationState", existing.notificationState),
|
|
138
|
+
recovery_bundle_id: pick("recoveryBundleId", existing.recoveryBundleId),
|
|
139
|
+
revert_commit_id: pick("revertCommitId", existing.revertCommitId),
|
|
140
|
+
superseded_by_handoff_id: pick("supersededByHandoffId", existing.supersededByHandoffId),
|
|
141
|
+
error_code: pick("errorCode", existing.errorCode),
|
|
142
|
+
error_detail: pick("errorDetail", existing.errorDetail),
|
|
143
|
+
updated_at_ms: now,
|
|
144
|
+
handoff_id: input.handoffId,
|
|
145
|
+
binding_id: input.bindingId,
|
|
146
|
+
});
|
|
147
|
+
return getApplication(store, input.handoffId, input.bindingId);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
export function transitionApplicationState(store, handoffId, bindingId, toState) {
|
|
151
|
+
return withTransaction(store, () => {
|
|
152
|
+
const existing = getApplication(store, handoffId, bindingId);
|
|
153
|
+
if (!existing) {
|
|
154
|
+
throw new Error(`No inbound application for (handoff ${handoffId}, binding ${bindingId})`);
|
|
155
|
+
}
|
|
156
|
+
if (!isLegalApplicationTransition(existing.state, toState)) {
|
|
157
|
+
throw new IllegalApplicationTransitionError(existing.state, toState);
|
|
158
|
+
}
|
|
159
|
+
store.db
|
|
160
|
+
.prepare(`UPDATE inbound_handoff_applications
|
|
161
|
+
SET state = :state, updated_at_ms = :now
|
|
162
|
+
WHERE handoff_id = :handoff_id AND binding_id = :binding_id`)
|
|
163
|
+
.run({ state: toState, now: Date.now(), handoff_id: handoffId, binding_id: bindingId });
|
|
164
|
+
return getApplication(store, handoffId, bindingId);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
function mapRow(row) {
|
|
2
|
+
return {
|
|
3
|
+
id: row.id,
|
|
4
|
+
bindingId: row.binding_id,
|
|
5
|
+
decision: row.decision,
|
|
6
|
+
oldBaselineCommitId: row.old_baseline_commit_id ?? null,
|
|
7
|
+
newBaselineCommitId: row.new_baseline_commit_id,
|
|
8
|
+
handoffId: row.handoff_id ?? null,
|
|
9
|
+
createdAtMs: row.created_at_ms,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function insertHistoryDecision(store, input) {
|
|
13
|
+
store.db
|
|
14
|
+
.prepare(`INSERT INTO local_history_decisions (
|
|
15
|
+
id, binding_id, decision, old_baseline_commit_id,
|
|
16
|
+
new_baseline_commit_id, handoff_id, created_at_ms
|
|
17
|
+
) VALUES (
|
|
18
|
+
:id, :binding_id, :decision, :old_baseline_commit_id,
|
|
19
|
+
:new_baseline_commit_id, :handoff_id, :created_at_ms
|
|
20
|
+
)`)
|
|
21
|
+
.run({
|
|
22
|
+
id: input.id,
|
|
23
|
+
binding_id: input.bindingId,
|
|
24
|
+
decision: input.decision,
|
|
25
|
+
old_baseline_commit_id: input.oldBaselineCommitId ?? null,
|
|
26
|
+
new_baseline_commit_id: input.newBaselineCommitId,
|
|
27
|
+
handoff_id: input.handoffId ?? null,
|
|
28
|
+
created_at_ms: Date.now(),
|
|
29
|
+
});
|
|
30
|
+
return getHistoryDecision(store, input.id);
|
|
31
|
+
}
|
|
32
|
+
export function getHistoryDecision(store, id) {
|
|
33
|
+
const row = store.db
|
|
34
|
+
.prepare("SELECT * FROM local_history_decisions WHERE id = :id")
|
|
35
|
+
.get({ id });
|
|
36
|
+
return row ? mapRow(row) : null;
|
|
37
|
+
}
|
|
38
|
+
export function listHistoryDecisions(store, bindingId) {
|
|
39
|
+
const rows = store.db
|
|
40
|
+
.prepare(`SELECT * FROM local_history_decisions
|
|
41
|
+
WHERE binding_id = :binding_id
|
|
42
|
+
ORDER BY created_at_ms DESC, id DESC`)
|
|
43
|
+
.all({ binding_id: bindingId });
|
|
44
|
+
return rows.map(mapRow);
|
|
45
|
+
}
|
|
46
|
+
export function latestHistoryDecision(store, bindingId) {
|
|
47
|
+
return listHistoryDecisions(store, bindingId)[0] ?? null;
|
|
48
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { openLocalStore, withTransaction, } from "./database.js";
|
|
2
|
+
export { nustackHomeDir, stateDbPath, locksDir, cacheDir, recoveryDir, ensureNuStackLayout, } from "./paths.js";
|
|
3
|
+
export { MIGRATIONS, LATEST_SCHEMA_VERSION, LocalSchemaTooNewError, migrateToLatest, } from "./migrations.js";
|
|
4
|
+
export { INBOUND_APPLICATION_STATES, BINDING_STATUSES, LIVE_BINDING_STATUSES, INBOUND_APPLICATION_TRANSITIONS, isLegalApplicationTransition, } from "./sync_vocabulary.js";
|
|
5
|
+
export * from "./device_state.js";
|
|
6
|
+
export * from "./bindings.js";
|
|
7
|
+
export * from "./commit_origins.js";
|
|
8
|
+
export * from "./handoff_applications.js";
|
|
9
|
+
export * from "./recovery_bundles.js";
|
|
10
|
+
export * from "./pending_uploads.js";
|
|
11
|
+
export * from "./history_decisions.js";
|
|
12
|
+
export * from "./accepted_uploads.js";
|
|
13
|
+
export * from "./workspace_events_cache.js";
|
|
14
|
+
export * from "./filebeat_state.js";
|
|
@@ -0,0 +1,240 @@
|
|
|
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
|
+
export const MIGRATIONS = [
|
|
197
|
+
MIGRATION_V1,
|
|
198
|
+
MIGRATION_V2,
|
|
199
|
+
MIGRATION_V3,
|
|
200
|
+
MIGRATION_V4,
|
|
201
|
+
MIGRATION_V5,
|
|
202
|
+
MIGRATION_V6,
|
|
203
|
+
];
|
|
204
|
+
export const LATEST_SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
|
|
205
|
+
export class LocalSchemaTooNewError extends Error {
|
|
206
|
+
foundVersion;
|
|
207
|
+
supportedVersion;
|
|
208
|
+
constructor(foundVersion, supportedVersion) {
|
|
209
|
+
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.`);
|
|
210
|
+
this.name = "LocalSchemaTooNewError";
|
|
211
|
+
this.foundVersion = foundVersion;
|
|
212
|
+
this.supportedVersion = supportedVersion;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function readUserVersion(db) {
|
|
216
|
+
const row = db.prepare("PRAGMA user_version").get();
|
|
217
|
+
return typeof row?.user_version === "number" ? row.user_version : 0;
|
|
218
|
+
}
|
|
219
|
+
export function migrateToLatest(db) {
|
|
220
|
+
const current = readUserVersion(db);
|
|
221
|
+
if (current > LATEST_SCHEMA_VERSION) {
|
|
222
|
+
throw new LocalSchemaTooNewError(current, LATEST_SCHEMA_VERSION);
|
|
223
|
+
}
|
|
224
|
+
for (const migration of MIGRATIONS) {
|
|
225
|
+
if (migration.version <= current)
|
|
226
|
+
continue;
|
|
227
|
+
db.exec("BEGIN IMMEDIATE");
|
|
228
|
+
try {
|
|
229
|
+
for (const statement of migration.statements) {
|
|
230
|
+
db.exec(statement);
|
|
231
|
+
}
|
|
232
|
+
db.exec(`PRAGMA user_version = ${migration.version}`);
|
|
233
|
+
db.exec("COMMIT");
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
db.exec("ROLLBACK");
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { mkdirSecure } from "../secure_file.js";
|
|
4
|
+
export function nustackHomeDir() {
|
|
5
|
+
return path.join(os.homedir(), ".nustack");
|
|
6
|
+
}
|
|
7
|
+
export function stateDbPath() {
|
|
8
|
+
return path.join(nustackHomeDir(), "state.db");
|
|
9
|
+
}
|
|
10
|
+
export function locksDir() {
|
|
11
|
+
return path.join(nustackHomeDir(), "locks");
|
|
12
|
+
}
|
|
13
|
+
export function cacheDir() {
|
|
14
|
+
return path.join(nustackHomeDir(), "cache");
|
|
15
|
+
}
|
|
16
|
+
export function recoveryDir() {
|
|
17
|
+
return path.join(nustackHomeDir(), "recovery");
|
|
18
|
+
}
|
|
19
|
+
export async function ensureNuStackLayout() {
|
|
20
|
+
await mkdirSecure(nustackHomeDir());
|
|
21
|
+
await mkdirSecure(locksDir());
|
|
22
|
+
await mkdirSecure(cacheDir());
|
|
23
|
+
await mkdirSecure(recoveryDir());
|
|
24
|
+
}
|