@nurix/nustack 0.10.0-dev.13 → 0.10.0-dev.14

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 (52) hide show
  1. package/dist/commands/bootstrap.js +4 -4
  2. package/dist/commands/docs.js +13 -13
  3. package/dist/commands/doctor.js +13 -13
  4. package/dist/commands/handoff.js +8 -8
  5. package/dist/commands/init.js +43 -43
  6. package/dist/commands/{workspace-sync.js → project-sync.js} +5 -5
  7. package/dist/commands/{workspace.js → project.js} +133 -133
  8. package/dist/commands/review.js +5 -5
  9. package/dist/commands/search.js +7 -7
  10. package/dist/commands/sessions.js +1 -1
  11. package/dist/commands/sync.js +10 -10
  12. package/dist/commands/toolkit.js +19 -19
  13. package/dist/index.js +29 -29
  14. package/dist/lib/etna/profile.js +9 -9
  15. package/dist/lib/etna/reconcile.js +11 -11
  16. package/dist/lib/git-sync/core/engine.js +4 -4
  17. package/dist/lib/git-sync/inbound/apply_commit.js +3 -3
  18. package/dist/lib/git-sync/inbound/approval.js +1 -1
  19. package/dist/lib/git-sync/inbound/download.js +8 -8
  20. package/dist/lib/git-sync/inbound/event_sync.js +13 -13
  21. package/dist/lib/git-sync/inbound/notification.js +1 -1
  22. package/dist/lib/git-sync/outbound/extraction.js +4 -4
  23. package/dist/lib/git-sync/outbound/history_decision.js +2 -2
  24. package/dist/lib/git-sync/outbound/publisher.js +2 -2
  25. package/dist/lib/git-sync/outbound/queue.js +3 -3
  26. package/dist/lib/git-sync/outbound/upload_client.js +4 -4
  27. package/dist/lib/git-sync/whitelist/index.js +1 -1
  28. package/dist/lib/git-sync/whitelist/types.js +1 -1
  29. package/dist/lib/git-sync/whitelist/validation.js +5 -5
  30. package/dist/lib/lanes/supervisor.js +8 -8
  31. package/dist/lib/local_store/accepted_uploads.js +5 -5
  32. package/dist/lib/local_store/bindings.js +11 -11
  33. package/dist/lib/local_store/index.js +1 -1
  34. package/dist/lib/local_store/migrations.js +13 -0
  35. package/dist/lib/local_store/project_events_cache.js +54 -0
  36. package/dist/lib/nustack_client.js +45 -45
  37. package/dist/lib/onboarding/attach_existing_git.js +7 -7
  38. package/dist/lib/onboarding/binding.js +11 -11
  39. package/dist/lib/onboarding/folder_state.js +10 -10
  40. package/dist/lib/onboarding/handoff_apply.js +2 -2
  41. package/dist/lib/onboarding/init_fresh_folder.js +18 -18
  42. package/dist/lib/onboarding/manifest_writer.js +31 -31
  43. package/dist/lib/onboarding/web_first_attach.js +12 -12
  44. package/dist/lib/{workspace_context.js → project_context.js} +24 -24
  45. package/dist/lib/review/client.js +1 -1
  46. package/dist/lib/session_telemetry.js +2 -2
  47. package/dist/lib/sessions/client.js +8 -8
  48. package/dist/lib/sessions/outsideSessions.js +3 -3
  49. package/dist/lib/telemetry/config_renderer.js +3 -3
  50. package/dist/lib/telemetry/health_report.js +1 -1
  51. package/package.json +3 -3
  52. package/dist/lib/local_store/workspace_events_cache.js +0 -54
@@ -1,9 +1,9 @@
1
1
  import path from "node:path";
2
2
  import * as p from "@clack/prompts";
3
- import { exchangeServiceCredential, fetchServiceCredential, listWorkspaceIntegrations, resolveNuStackUrl, } from "../lib/nustack_client.js";
3
+ import { exchangeServiceCredential, fetchServiceCredential, listProjectIntegrations, resolveNuStackUrl, } from "../lib/nustack_client.js";
4
4
  import { readConsumesEdges } from "../lib/manifest.js";
5
5
  import { ensureAccessToken, signedOutCopy } from "../lib/auth_grant.js";
6
- import { resolveWorkspaceId } from "../lib/workspace_context.js";
6
+ import { resolveProjectId } from "../lib/project_context.js";
7
7
  import { readManagedValues, writeManagedValues } from "../lib/env_file.js";
8
8
  import { printJsonError, printJsonSuccess } from "../lib/json_output.js";
9
9
  const COMMAND = "sync";
@@ -18,23 +18,23 @@ export async function runSync(options) {
18
18
  if (!deviceAuth)
19
19
  return failSync(json, "SIGNED_OUT", signedOut(), "The managed block was not touched.");
20
20
  const accessToken = deviceAuth.accessToken;
21
- const workspace = await resolveWorkspaceId(cwd, { workspace: options.workspace });
22
- if (!workspace.ok)
23
- return failSync(json, "WORKSPACE_UNRESOLVED", workspace.message, "The managed block was not touched.");
21
+ const project = await resolveProjectId(cwd, { project: options.project });
22
+ if (!project.ok)
23
+ return failSync(json, "PROJECT_UNRESOLVED", project.message, "The managed block was not touched.");
24
24
  const spinner = json ? null : p.spinner();
25
25
  spinner?.start("Exchanging declared services");
26
26
  let activeServices;
27
27
  let projections;
28
28
  try {
29
29
  for (const edge of await readConsumesEdges(cwd)) {
30
- await exchangeServiceCredential(baseUrl, accessToken, { workspaceId: workspace.workspaceId, service: edge.service });
30
+ await exchangeServiceCredential(baseUrl, accessToken, { projectId: project.projectId, service: edge.service });
31
31
  }
32
32
  spinner?.message("Enumerating active integrations");
33
- const integrations = await listWorkspaceIntegrations(baseUrl, accessToken, workspace.workspaceId);
33
+ const integrations = await listProjectIntegrations(baseUrl, accessToken, project.projectId);
34
34
  activeServices = integrations.filter((i) => i.status === "active").map((i) => i.serviceSlug);
35
35
  projections = [];
36
36
  for (const service of activeServices) {
37
- projections.push(await fetchServiceCredential(baseUrl, accessToken, { workspaceId: workspace.workspaceId, service }));
37
+ projections.push(await fetchServiceCredential(baseUrl, accessToken, { projectId: project.projectId, service }));
38
38
  }
39
39
  }
40
40
  catch (error) {
@@ -59,7 +59,7 @@ export async function runSync(options) {
59
59
  await writeManagedValues(path.join(cwd, ".env.example"), placeholders);
60
60
  if (json) {
61
61
  printJsonSuccess(COMMAND, {
62
- workspaceId: workspace.workspaceId,
62
+ projectId: project.projectId,
63
63
  integrations: activeServices.map((service) => ({ service, status: "active" })),
64
64
  envKeysWritten: Object.keys(nextValues),
65
65
  removedKeys,
@@ -69,7 +69,7 @@ export async function runSync(options) {
69
69
  p.log.success(`Managed block rewritten: ${projections.length} service credential(s).`);
70
70
  if (removedKeys.length > 0)
71
71
  p.log.info(`Removed stale entries: ${removedKeys.join(", ")}`);
72
- p.outro("In sync with NuStack — Service credentials only (Documents & Git are `nustack workspace sync`).");
72
+ p.outro("In sync with NuStack — Service credentials only (Documents & Git are `nustack project sync`).");
73
73
  }
74
74
  function failSync(json, code, message, humanTail) {
75
75
  if (json) {
@@ -1,12 +1,12 @@
1
1
  import * as p from "@clack/prompts";
2
2
  import { resolveNuStackUrl } from "../lib/nustack_client.js";
3
3
  import { ensureAuthWithRecovery, lastAuthFailure } from "../lib/auth_recovery.js";
4
- import { resolveWorkspaceId } from "../lib/workspace_context.js";
4
+ import { resolveProjectId } from "../lib/project_context.js";
5
5
  import { isInteractive } from "../lib/interactive.js";
6
6
  import { openLocalStore } from "../lib/local_store/database.js";
7
7
  import { getBindingByRepositoryRoot } from "../lib/local_store/bindings.js";
8
8
  import { resolveRepository } from "../lib/git-sync/core/repository.js";
9
- import { readWorkspaceManifests, diagnoseManifests } from "../lib/onboarding/manifest_writer.js";
9
+ import { readProjectManifests, diagnoseManifests } from "../lib/onboarding/manifest_writer.js";
10
10
  import { fetchToolkitProfile, ToolkitProfileUnavailableError } from "../lib/etna/profile.js";
11
11
  import { buildReconcilePlan, applyReconcile, toolkitCompatibility } from "../lib/etna/reconcile.js";
12
12
  import { printJsonError, printJsonSuccess } from "../lib/json_output.js";
@@ -28,9 +28,9 @@ async function resolveContext(command, json, options, globals) {
28
28
  return null;
29
29
  }
30
30
  const baseUrl = resolveNuStackUrl(globals);
31
- const workspace = await resolveWorkspaceId(root, options);
32
- if (!workspace.ok) {
33
- emitFail(command, json, "WORKSPACE_CONTEXT_MISSING", workspace.message);
31
+ const project = await resolveProjectId(root, options);
32
+ if (!project.ok) {
33
+ emitFail(command, json, "PROJECT_CONTEXT_MISSING", project.message);
34
34
  return null;
35
35
  }
36
36
  const deviceAuth = await ensureAuthWithRecovery(baseUrl, json);
@@ -39,7 +39,7 @@ async function resolveContext(command, json, options, globals) {
39
39
  emitFail(command, json, failure.code, failure.message);
40
40
  return null;
41
41
  }
42
- return { baseUrl, accessToken: deviceAuth.accessToken, workspaceId: workspace.workspaceId, root };
42
+ return { baseUrl, accessToken: deviceAuth.accessToken, projectId: project.projectId, root };
43
43
  }
44
44
  export async function runToolkitStatus(options, globals) {
45
45
  const json = options.json === true;
@@ -48,7 +48,7 @@ export async function runToolkitStatus(options, globals) {
48
48
  return;
49
49
  let cloud;
50
50
  try {
51
- cloud = await fetchToolkitProfile(ctx.baseUrl, ctx.accessToken, ctx.workspaceId);
51
+ cloud = await fetchToolkitProfile(ctx.baseUrl, ctx.accessToken, ctx.projectId);
52
52
  }
53
53
  catch (error) {
54
54
  if (error instanceof ToolkitProfileUnavailableError) {
@@ -57,24 +57,24 @@ export async function runToolkitStatus(options, globals) {
57
57
  }
58
58
  throw error;
59
59
  }
60
- const manifests = await readWorkspaceManifests(ctx.root);
61
- const diagnosis = diagnoseManifests({ name: cloud.name, version: cloud.version }, manifests.workspaceManifest?.toolkit ?? null, manifests.serviceToolkit);
60
+ const manifests = await readProjectManifests(ctx.root);
61
+ const diagnosis = diagnoseManifests({ name: cloud.name, version: cloud.version }, manifests.projectManifest?.toolkit ?? null, manifests.serviceToolkit);
62
62
  const binding = await withStore((store) => getBindingByRepositoryRoot(store, ctx.root));
63
63
  const compatibility = toolkitCompatibility(binding ?? { etnaProfileJson: null }, cloud);
64
64
  if (json) {
65
65
  printJsonSuccess("toolkit status", {
66
- workspaceId: ctx.workspaceId,
66
+ projectId: ctx.projectId,
67
67
  cloud,
68
- workspaceJson: manifests.workspaceManifest?.toolkit ?? null,
68
+ projectJson: manifests.projectManifest?.toolkit ?? null,
69
69
  serviceJson: manifests.serviceToolkit,
70
70
  diagnosis,
71
71
  compatibility,
72
72
  });
73
73
  return;
74
74
  }
75
- console.log(`Toolkit for Workspace ${ctx.workspaceId}`);
75
+ console.log(`Toolkit for Project ${ctx.projectId}`);
76
76
  console.log(` cloud pin: ${cloud.name}@${cloud.version} (renderer ${cloud.rendererVersion}, placement ${cloud.placementMapVersion})`);
77
- console.log(` .nustack/app.json: ${fmtToolkit(manifests.workspaceManifest?.toolkit)}`);
77
+ console.log(` .nustack/app.json: ${fmtToolkit(manifests.projectManifest?.toolkit)}`);
78
78
  console.log(` nustack.service.json:${fmtToolkit(manifests.serviceToolkit)}`);
79
79
  console.log(` diagnosis: ${diagnosis}`);
80
80
  console.log(` write gate: ${compatibility.blocksWrites ? `BLOCKED — ${compatibility.reason}` : "clear (projection and sync writes allowed)"}`);
@@ -89,7 +89,7 @@ export async function runToolkitReconcile(options, globals) {
89
89
  return;
90
90
  let cloud;
91
91
  try {
92
- cloud = await fetchToolkitProfile(ctx.baseUrl, ctx.accessToken, ctx.workspaceId);
92
+ cloud = await fetchToolkitProfile(ctx.baseUrl, ctx.accessToken, ctx.projectId);
93
93
  }
94
94
  catch (error) {
95
95
  if (error instanceof ToolkitProfileUnavailableError) {
@@ -99,12 +99,12 @@ export async function runToolkitReconcile(options, globals) {
99
99
  throw error;
100
100
  }
101
101
  const binding = await withStore((store) => getBindingByRepositoryRoot(store, ctx.root));
102
- const manifests = await readWorkspaceManifests(ctx.root);
103
- const identity = manifests.workspaceManifest
104
- ? { workspaceId: manifests.workspaceManifest.workspaceId, organizationId: manifests.workspaceManifest.organizationId }
105
- : manifests.serviceWorkspaceBinding;
102
+ const manifests = await readProjectManifests(ctx.root);
103
+ const identity = manifests.projectManifest
104
+ ? { projectId: manifests.projectManifest.workspaceId, organizationId: manifests.projectManifest.organizationId }
105
+ : manifests.serviceProjectBinding;
106
106
  if (!identity) {
107
- emitFail("toolkit reconcile", json, "WORKSPACE_CONTEXT_MISSING", "No `.nustack/app.json` binding to reconcile — attach the repository first.");
107
+ emitFail("toolkit reconcile", json, "PROJECT_CONTEXT_MISSING", "No `.nustack/app.json` binding to reconcile — attach the repository first.");
108
108
  return;
109
109
  }
110
110
  const plan = await buildReconcilePlan(ctx.root, binding ?? { etnaProfileJson: null }, cloud);
package/dist/index.js CHANGED
@@ -16,8 +16,8 @@ import { runLogout } from "./commands/logout.js";
16
16
  import { runTelemetry } from "./commands/telemetry.js";
17
17
  import { runSearch, runSearchServices } from "./commands/search.js";
18
18
  import { runDeviceList, runDeviceRevoke, runDeviceStatus } from "./commands/device.js";
19
- import { registerWorkspaceSyncCommands } from "./commands/workspace-sync.js";
20
- import { registerWorkspaceCommands } from "./commands/workspace.js";
19
+ import { registerProjectSyncCommands } from "./commands/project-sync.js";
20
+ import { registerProjectCommands } from "./commands/project.js";
21
21
  import { runOrganizationList, runOrganizationCreate, runOrganizationUse } from "./commands/organization.js";
22
22
  import { runDocsList, runDocsShow, runDocsStatus } from "./commands/docs.js";
23
23
  import { runHandoffList, runHandoffShow } from "./commands/handoff.js";
@@ -52,8 +52,8 @@ program
52
52
  .action(async (options) => runLogout(program.opts(), { forgetDevice: options.forgetDevice }));
53
53
  program
54
54
  .command("init")
55
- .description("Onboard this folder: sign in, then bind it to a Workspace (the onboarding router)")
56
- .option("--organization <id>", "the Organization to create or select the Workspace in")
55
+ .description("Onboard this folder: sign in, then bind it to a Project (the onboarding router)")
56
+ .option("--organization <id>", "the Organization to create or select the Project in")
57
57
  .option("--yes", "pre-confirm the populated-folder onboarding guard")
58
58
  .action(async (options) => runInit({ ...program.opts(), ...options }));
59
59
  program
@@ -69,8 +69,8 @@ program
69
69
  .action(async (options) => runList(options, program.opts()));
70
70
  program
71
71
  .command("sync")
72
- .description("Project this Workspace's Service credentials into the managed .env block (not Documents or Git)")
73
- .option("--workspace <id>", "the Workspace to act as (defaults to nustack.service.json / NUSTACK_WORKSPACE_ID)")
72
+ .description("Project this Project's Service credentials into the managed .env block (not Documents or Git)")
73
+ .option("--project <id>", "the Project to act as (defaults to nustack.service.json / NUSTACK_WORKSPACE_ID)")
74
74
  .option("--json", "emit the versioned machine envelope instead of interactive output")
75
75
  .action(async (options) => runSync({ ...program.opts(), ...options }));
76
76
  program
@@ -98,7 +98,7 @@ program
98
98
  program
99
99
  .command("doctor")
100
100
  .description("Run the full local diagnostics matrix (identity, repository, manifests, toolkit, telemetry, credentials)")
101
- .option("--workspace <id>", "the Workspace to diagnose (defaults to the bound repository)")
101
+ .option("--project <id>", "the Project to diagnose (defaults to the bound repository)")
102
102
  .option("--json", "emit the versioned machine envelope (secrets and absolute paths redacted)")
103
103
  .option("--local-verbose", "include Device-local absolute paths in the output")
104
104
  .action(async (options) => runDoctor({ ...program.opts(), ...options }));
@@ -144,9 +144,9 @@ program
144
144
  const toLimit = (value) => Number.parseInt(value, 10);
145
145
  const search = program
146
146
  .command("search [keywords...]")
147
- .description("Search the NuStack Directory for workspaces by keyword")
147
+ .description("Search the NuStack Directory for projects by keyword")
148
148
  .option("--json", "emit the raw results array as JSON")
149
- .option("--category <cat>", "filter workspaces by category")
149
+ .option("--category <cat>", "filter projects by category")
150
150
  .option("--limit <n>", "max results (default 20)", toLimit)
151
151
  .action(async (keywords, options) => runSearch(keywords, options, program.opts()));
152
152
  search
@@ -156,8 +156,8 @@ search
156
156
  .option("--type <type>", "filter services by type")
157
157
  .option("--limit <n>", "max results (default 20)", toLimit)
158
158
  .action(async (keywords, options) => runSearchServices(keywords, { ...search.opts(), ...options }, program.opts()));
159
- search.addHelpText("after", '\n`services` is a reserved subcommand. To search workspaces for the literal word "services", use the\n' +
160
- "explicit separator: `nustack search -- services` (everything after `--` is an workspaces query).");
159
+ search.addHelpText("after", '\n`services` is a reserved subcommand. To search projects for the literal word "services", use the\n' +
160
+ "explicit separator: `nustack search -- services` (everything after `--` is an projects query).");
161
161
  const organization = program.command("organization").description("Work with your NuStack Organizations");
162
162
  organization
163
163
  .command("list")
@@ -174,40 +174,40 @@ organization
174
174
  .description("Set the active Organization context for this device")
175
175
  .option("--json", "emit the versioned machine envelope")
176
176
  .action(async (target, options) => runOrganizationUse(target, options, program.opts()));
177
- const workspace = program.command("workspace").description("Work with NuStack Workspaces");
178
- registerWorkspaceCommands(workspace, () => program.opts());
179
- registerWorkspaceSyncCommands(workspace, () => program.opts());
177
+ const project = program.command("project").description("Work with NuStack Projects");
178
+ registerProjectCommands(project, () => program.opts());
179
+ registerProjectSyncCommands(project, () => program.opts());
180
180
  registerReviewCommands(program.command("review").description("Read and mirror NuStack Review items"), () => program.opts());
181
- const docs = program.command("docs").description("Work with a Workspace's Documents");
181
+ const docs = program.command("docs").description("Work with a Project's Documents");
182
182
  docs
183
183
  .command("list")
184
- .description("List the Workspace's Documents")
185
- .option("--workspace <id>", "the Workspace to act as (defaults to the bound repository)")
184
+ .description("List the Project's Documents")
185
+ .option("--project <id>", "the Project to act as (defaults to the bound repository)")
186
186
  .option("--json", "emit the versioned machine envelope")
187
187
  .action(async (options) => runDocsList(options, program.opts()));
188
188
  docs
189
189
  .command("show <slug-or-id>")
190
190
  .description("Show a Document: a bounded inline body or its content URL")
191
- .option("--workspace <id>", "the Workspace to act as (defaults to the bound repository)")
191
+ .option("--project <id>", "the Project to act as (defaults to the bound repository)")
192
192
  .option("--json", "emit the versioned machine envelope")
193
193
  .action(async (target, options) => runDocsShow(target, options, program.opts()));
194
194
  docs
195
195
  .command("status")
196
196
  .description("Report per-path local/cloud Document convergence for the bound repository")
197
- .option("--workspace <id>", "the Workspace to act as (defaults to the bound repository)")
197
+ .option("--project <id>", "the Project to act as (defaults to the bound repository)")
198
198
  .option("--json", "emit the versioned machine envelope")
199
199
  .action(async (options) => runDocsStatus(options, program.opts()));
200
- const handoff = program.command("handoff").description("Work with a Workspace's Handoffs");
200
+ const handoff = program.command("handoff").description("Work with a Project's Handoffs");
201
201
  handoff
202
202
  .command("list")
203
- .description("List the Workspace's Handoffs")
204
- .option("--workspace <id>", "the Workspace to act as (defaults to the bound repository)")
203
+ .description("List the Project's Handoffs")
204
+ .option("--project <id>", "the Project to act as (defaults to the bound repository)")
205
205
  .option("--json", "emit the versioned machine envelope")
206
206
  .action(async (options) => runHandoffList(options, program.opts()));
207
207
  handoff
208
208
  .command("show <id>")
209
209
  .description("Show a Handoff (add --targets for the binding-scoped per-target manifest)")
210
- .option("--workspace <id>", "the Workspace to act as (defaults to the bound repository)")
210
+ .option("--project <id>", "the Project to act as (defaults to the bound repository)")
211
211
  .option("--targets", "list each target's path, operation, and base semantics")
212
212
  .option("--json", "emit the versioned machine envelope")
213
213
  .action(async (id, options) => runHandoffShow(id, options, program.opts()));
@@ -223,17 +223,17 @@ sessions
223
223
  .option("--scan <json>", "the scan payload as JSON, or @<path> to read it from a file")
224
224
  .option("--json", "emit the versioned machine envelope")
225
225
  .action(async (options) => runSessionsRecord(options, program.opts()));
226
- const toolkit = program.command("toolkit").description("Inspect and reconcile the Workspace's toolkit pin");
226
+ const toolkit = program.command("toolkit").description("Inspect and reconcile the Project's toolkit pin");
227
227
  toolkit
228
228
  .command("status")
229
229
  .description("Compare the cloud pin against .nustack/workspace.json and nustack.service.json (F-026)")
230
- .option("--workspace <id>", "the Workspace to act as (defaults to the bound repository)")
230
+ .option("--project <id>", "the Project to act as (defaults to the bound repository)")
231
231
  .option("--json", "emit the versioned machine envelope")
232
232
  .action(async (options) => runToolkitStatus(options, program.opts()));
233
233
  toolkit
234
234
  .command("reconcile")
235
235
  .description("Preview and apply the deterministic toolkit repair (no User content is touched)")
236
- .option("--workspace <id>", "the Workspace to act as (defaults to the bound repository)")
236
+ .option("--project <id>", "the Project to act as (defaults to the bound repository)")
237
237
  .option("--yes", "apply the deterministic preview without prompting")
238
238
  .option("--json", "emit the versioned machine envelope")
239
239
  .action(async (options) => runToolkitReconcile(options, program.opts()));
@@ -253,14 +253,14 @@ agentHook
253
253
  .action(async () => runAgentHookRemove());
254
254
  function describeError(error) {
255
255
  const directCode = error?.code;
256
- if (directCode === "WORKSPACE_CONTEXT_MISSING") {
257
- return "no Workspace in context — run inside a bound repository (`.nustack/workspace.json`) or pass `--workspace <id>`.";
256
+ if (directCode === "PROJECT_CONTEXT_MISSING") {
257
+ return "no Project in context — run inside a bound repository (`.nustack/workspace.json`) or pass `--project <id>`.";
258
258
  }
259
259
  if (directCode === "ORGANIZATION_CONTEXT_MISSING") {
260
260
  return "no Organization in context — run `nustack organization use <id>` or pass `--organization <id>`.";
261
261
  }
262
262
  if (directCode === "TOOLKIT_INCOMPATIBLE") {
263
- return "the installed toolkit is incompatible with the Workspace's cloud pin — run `nustack toolkit reconcile`.";
263
+ return "the installed toolkit is incompatible with the Project's cloud pin — run `nustack toolkit reconcile`.";
264
264
  }
265
265
  const cause = error?.cause;
266
266
  const code = cause?.code;
@@ -1,17 +1,17 @@
1
- import { getWorkspace } from "../nustack_client.js";
1
+ import { getProject } from "../nustack_client.js";
2
2
  export class ToolkitProfileUnavailableError extends Error {
3
- workspaceId;
4
- constructor(workspaceId) {
5
- super("The Workspace summary did not project a Toolkit Profile pin — the owning server plan must expose it on GET /api/v2/workspaces/{workspaceId}.");
3
+ projectId;
4
+ constructor(projectId) {
5
+ super("The Project summary did not project a Toolkit Profile pin — the owning server plan must expose it on GET /api/v2/projects/{projectId}.");
6
6
  this.name = "ToolkitProfileUnavailableError";
7
- this.workspaceId = workspaceId;
7
+ this.projectId = projectId;
8
8
  }
9
9
  }
10
- export async function fetchToolkitProfile(baseUrl, accessToken, workspaceId) {
11
- const workspace = await getWorkspace(baseUrl, accessToken, workspaceId);
12
- const toolkit = workspace.toolkit;
10
+ export async function fetchToolkitProfile(baseUrl, accessToken, projectId) {
11
+ const project = await getProject(baseUrl, accessToken, projectId);
12
+ const toolkit = project.toolkit;
13
13
  if (!toolkit || typeof toolkit.name !== "string" || typeof toolkit.version !== "string") {
14
- throw new ToolkitProfileUnavailableError(workspaceId);
14
+ throw new ToolkitProfileUnavailableError(projectId);
15
15
  }
16
16
  return {
17
17
  name: toolkit.name,
@@ -1,4 +1,4 @@
1
- import { writeWorkspaceManifests, readWorkspaceManifests, diagnoseManifests, } from "../onboarding/manifest_writer.js";
1
+ import { writeProjectManifests, readProjectManifests, diagnoseManifests, } from "../onboarding/manifest_writer.js";
2
2
  import { serializeProfileForBinding } from "./profile.js";
3
3
  import { updateBindingEtnaProfile } from "../local_store/bindings.js";
4
4
  export function parseRecordedToolkit(binding) {
@@ -35,16 +35,16 @@ export function toolkitCompatibility(binding, cloud) {
35
35
  return { compatible: true, blocksWrites: false, reason: null };
36
36
  }
37
37
  export async function buildReconcilePlan(root, binding, cloud) {
38
- const manifests = await readWorkspaceManifests(root);
38
+ const manifests = await readProjectManifests(root);
39
39
  const cloudId = { name: cloud.name, version: cloud.version };
40
- const diagnosis = diagnoseManifests(cloudId, manifests.workspaceManifest?.toolkit ?? null, manifests.serviceToolkit);
40
+ const diagnosis = diagnoseManifests(cloudId, manifests.projectManifest?.toolkit ?? null, manifests.serviceToolkit);
41
41
  const changes = [];
42
- const workspace = manifests.workspaceManifest?.toolkit;
43
- if (workspace) {
44
- pushIf(changes, ".nustack/workspace.json", "toolkit.name", workspace.name, cloud.name);
45
- pushIf(changes, ".nustack/workspace.json", "toolkit.version", workspace.version, cloud.version);
46
- pushIf(changes, ".nustack/workspace.json", "toolkit.rendererVersion", workspace.rendererVersion, cloud.rendererVersion);
47
- pushIf(changes, ".nustack/workspace.json", "toolkit.placementMapVersion", workspace.placementMapVersion, cloud.placementMapVersion);
42
+ const project = manifests.projectManifest?.toolkit;
43
+ if (project) {
44
+ pushIf(changes, ".nustack/workspace.json", "toolkit.name", project.name, cloud.name);
45
+ pushIf(changes, ".nustack/workspace.json", "toolkit.version", project.version, cloud.version);
46
+ pushIf(changes, ".nustack/workspace.json", "toolkit.rendererVersion", project.rendererVersion, cloud.rendererVersion);
47
+ pushIf(changes, ".nustack/workspace.json", "toolkit.placementMapVersion", project.placementMapVersion, cloud.placementMapVersion);
48
48
  }
49
49
  const svc = manifests.serviceToolkit;
50
50
  if (svc) {
@@ -63,8 +63,8 @@ function pushIf(changes, file, field, from, to) {
63
63
  changes.push({ file, field, from, to });
64
64
  }
65
65
  export async function applyReconcile(store, bindingId, root, identity, cloud) {
66
- await writeWorkspaceManifests(root, {
67
- workspaceId: identity.workspaceId,
66
+ await writeProjectManifests(root, {
67
+ projectId: identity.projectId,
68
68
  organizationId: identity.organizationId,
69
69
  toolkit: {
70
70
  name: cloud.name,
@@ -10,7 +10,7 @@ const DEFAULT_HEARTBEAT_MS = 30_000;
10
10
  export function observeBinding(binding) {
11
11
  const base = {
12
12
  bindingId: binding.id,
13
- workspaceId: binding.workspaceId,
13
+ projectId: binding.projectId,
14
14
  boundBranch: binding.boundBranch,
15
15
  status: binding.status,
16
16
  resolved: false,
@@ -18,7 +18,7 @@ export function observeBinding(binding) {
18
18
  operationState: "unknown",
19
19
  headCommitId: null,
20
20
  lastProcessedCommitId: binding.lastProcessedCommitId,
21
- lastAppliedWorkspaceSequence: binding.lastAppliedWorkspaceSequence,
21
+ lastAppliedProjectSequence: binding.lastAppliedProjectSequence,
22
22
  pendingCommitCount: 0,
23
23
  };
24
24
  let repo;
@@ -209,12 +209,12 @@ export function createSyncEngine(options) {
209
209
  safeEmit({
210
210
  event: "binding_status",
211
211
  bindingId: observation.bindingId,
212
- workspaceId: observation.workspaceId,
212
+ projectId: observation.projectId,
213
213
  boundBranch: observation.boundBranch,
214
214
  status: observation.status,
215
215
  headCommitId: observation.headCommitId,
216
216
  lastProcessedCommitId: observation.lastProcessedCommitId,
217
- lastAppliedWorkspaceSequence: observation.lastAppliedWorkspaceSequence,
217
+ lastAppliedProjectSequence: observation.lastAppliedProjectSequence,
218
218
  pendingCommitCount: observation.pendingCommitCount,
219
219
  });
220
220
  }
@@ -11,12 +11,12 @@ import { readTargetState } from "./content_decision.js";
11
11
  import { computeStateFingerprint } from "./state_fingerprint.js";
12
12
  import { persistRecoveryBundle } from "../recovery/store.js";
13
13
  export const NUSTACK_SYNC_IDENTITY = { name: "NuStack Sync", email: "sync@nustack.local" };
14
- export function buildCommitMessage(handoffId, workspaceId, approvedByUserId) {
14
+ export function buildCommitMessage(handoffId, projectId, approvedByUserId) {
15
15
  const lines = [
16
16
  `nustack(sync): apply cloud handoff ${handoffId.slice(0, 12)}`,
17
17
  "",
18
18
  `NuStack-Handoff: ${handoffId}`,
19
- `NuStack-Workspace: ${workspaceId}`,
19
+ `NuStack-Project: ${projectId}`,
20
20
  "NuStack-Origin: cloud",
21
21
  ];
22
22
  if (approvedByUserId)
@@ -186,7 +186,7 @@ export async function applyCommit(input) {
186
186
  if (target.operation === "upsert" && target.blobPath)
187
187
  bytesByPath.set(target.path, readFileSync(target.blobPath));
188
188
  }
189
- const message = buildCommitMessage(handoffId, manifest.workspaceId, approved ? (input.approvedByUserId ?? null) : null);
189
+ const message = buildCommitMessage(handoffId, manifest.projectId, approved ? (input.approvedByUserId ?? null) : null);
190
190
  const built = buildCommit(repo, oldHead, targets, bytesByPath, message, {
191
191
  authorName: NUSTACK_SYNC_IDENTITY.name,
192
192
  authorEmail: NUSTACK_SYNC_IDENTITY.email,
@@ -12,7 +12,7 @@ export const APPROVAL_RECOVERY_NOTE = "Approving overwrites the listed paths. Gi
12
12
  export function toDownloadBinding(binding) {
13
13
  return {
14
14
  id: binding.id,
15
- workspaceId: binding.workspaceId,
15
+ projectId: binding.projectId,
16
16
  boundBranch: binding.boundBranch,
17
17
  repositoryRoot: binding.repositoryRoot,
18
18
  etnaProfileJson: binding.etnaProfileJson,
@@ -27,7 +27,7 @@ function recomputeManifestHash(manifest) {
27
27
  const core = {
28
28
  schemaVersion: 1,
29
29
  handoffId: manifest.handoffId,
30
- workspaceId: manifest.workspaceId,
30
+ projectId: manifest.projectId,
31
31
  documentSetVersionId: manifest.documentSetVersionId,
32
32
  toolkit: {
33
33
  name: manifest.toolkit.name,
@@ -62,7 +62,7 @@ function toolkitTechnicalWait(binding, manifest) {
62
62
  return "harness_incompatible";
63
63
  return null;
64
64
  }
65
- function crossWorkspaceMarker(body, workspaceId) {
65
+ function crossProjectMarker(body, projectId) {
66
66
  if (!body.startsWith("---\n"))
67
67
  return false;
68
68
  const secondLineEnd = body.indexOf("\n", 4);
@@ -71,8 +71,8 @@ function crossWorkspaceMarker(body, workspaceId) {
71
71
  return false;
72
72
  const fence = body.indexOf("\n---", 4);
73
73
  const block = body.slice(0, fence === -1 ? body.length : fence);
74
- const match = /\n\s*workspace_id:\s*(\S+)/.exec(block);
75
- return match !== null && match[1] !== workspaceId;
74
+ const match = /\n\s*project_id:\s*(\S+)/.exec(block);
75
+ return match !== null && match[1] !== projectId;
76
76
  }
77
77
  function isUnsupportedMedia(bytes) {
78
78
  for (const byte of bytes)
@@ -136,8 +136,8 @@ export async function downloadHandoff(deps, binding, handoffId) {
136
136
  const downloadContent = deps.downloadContent ?? downloadTargetContent;
137
137
  const fetchWhitelist = deps.fetchWhitelist ?? fetchSyncWhitelist;
138
138
  const manifest = await fetchManifest(deps.baseUrl, deps.accessToken, { handoffId, bindingId: binding.id });
139
- if (manifest.workspaceId !== binding.workspaceId)
140
- return reject("workspace_mismatch", "manifest Workspace does not match the binding");
139
+ if (manifest.projectId !== binding.projectId)
140
+ return reject("project_mismatch", "manifest Project does not match the binding");
141
141
  if (recomputeManifestHash(manifest) !== manifest.manifestSha256) {
142
142
  return reject("manifest_hash", "manifest hash does not verify");
143
143
  }
@@ -207,8 +207,8 @@ export async function downloadHandoff(deps, binding, handoffId) {
207
207
  const actualSha256 = sha256(bytes);
208
208
  if (actualSha256 !== target.targetSha256)
209
209
  return reject("hash_mismatch", "downloaded body hash does not match the manifest");
210
- if (crossWorkspaceMarker(new TextDecoder().decode(bytes), binding.workspaceId)) {
211
- return reject("cross_workspace_marker", "target body declares a foreign Workspace id");
210
+ if (crossProjectMarker(new TextDecoder().decode(bytes), binding.projectId)) {
211
+ return reject("cross_project_marker", "target body declares a foreign Project id");
212
212
  }
213
213
  const blobPath = blobCachePath(actualSha256);
214
214
  await mkdirSecure(path.dirname(blobPath));
@@ -1,6 +1,6 @@
1
1
  import { getBindingById, updateBindingCursor } from "../../local_store/bindings.js";
2
2
  import { hasCloudOriginForHandoff } from "../../local_store/commit_origins.js";
3
- import { cacheWorkspaceEvents, listCachedWorkspaceEvents, pruneWorkspaceEvents, } from "../../local_store/workspace_events_cache.js";
3
+ import { cacheProjectEvents, listCachedProjectEvents, pruneProjectEvents, } from "../../local_store/project_events_cache.js";
4
4
  import { getApplication, upsertApplication, transitionApplicationState, listApplicationsForBinding, } from "../../local_store/handoff_applications.js";
5
5
  import { fetchBindingEvents, acknowledgeBindingCursor } from "../../nustack_client.js";
6
6
  const PENDING_STATES = new Set([
@@ -29,7 +29,7 @@ function supersedeOlderApplications(store, bindingId, newerHandoffId) {
29
29
  return flipped;
30
30
  }
31
31
  function handoffIdOf(event) {
32
- return event.type === "version.created" && event.entity.kind === "workspace_document_version" ? event.entity.id : null;
32
+ return event.type === "version.created" && event.entity.kind === "project_document_version" ? event.entity.id : null;
33
33
  }
34
34
  export async function syncBindingEvents(store, deps, bindingId) {
35
35
  const binding = getBindingById(store, bindingId);
@@ -40,9 +40,9 @@ export async function syncBindingEvents(store, deps, bindingId) {
40
40
  createdHandoffs: [],
41
41
  supersededHandoffs: [],
42
42
  acknowledgedOwnOrigin: [],
43
- highestSequence: binding.lastAppliedWorkspaceSequence,
43
+ highestSequence: binding.lastAppliedProjectSequence,
44
44
  };
45
- let after = binding.lastAppliedWorkspaceSequence;
45
+ let after = binding.lastAppliedProjectSequence;
46
46
  for (;;) {
47
47
  const events = await fetchEvents(deps.baseUrl, deps.accessToken, {
48
48
  bindingId,
@@ -51,8 +51,8 @@ export async function syncBindingEvents(store, deps, bindingId) {
51
51
  });
52
52
  if (events.length === 0)
53
53
  break;
54
- cacheWorkspaceEvents(store, bindingId, events.map((event) => ({
55
- workspaceSequence: event.sequence,
54
+ cacheProjectEvents(store, bindingId, events.map((event) => ({
55
+ projectSequence: event.sequence,
56
56
  eventType: event.type,
57
57
  payloadJson: JSON.stringify({ entity: event.entity, payload: event.payload }),
58
58
  })));
@@ -81,7 +81,7 @@ export async function syncBindingEvents(store, deps, bindingId) {
81
81
  if (events.length < EVENTS_PAGE_LIMIT)
82
82
  break;
83
83
  }
84
- pruneWorkspaceEvents(store, bindingId);
84
+ pruneProjectEvents(store, bindingId);
85
85
  return result;
86
86
  }
87
87
  function cachedHandoffId(eventType, payloadJson) {
@@ -100,9 +100,9 @@ export async function advanceContiguousCursor(store, deps, bindingId) {
100
100
  if (!binding)
101
101
  throw new Error(`No binding "${bindingId}" on this machine.`);
102
102
  const acknowledgeCursor = deps.acknowledgeCursor ?? acknowledgeBindingCursor;
103
- let cursor = binding.lastAppliedWorkspaceSequence;
104
- for (const event of listCachedWorkspaceEvents(store, bindingId)) {
105
- if (event.workspaceSequence <= cursor)
103
+ let cursor = binding.lastAppliedProjectSequence;
104
+ for (const event of listCachedProjectEvents(store, bindingId)) {
105
+ if (event.projectSequence <= cursor)
106
106
  continue;
107
107
  const handoffId = cachedHandoffId(event.eventType, event.payloadJson);
108
108
  if (handoffId !== null) {
@@ -110,10 +110,10 @@ export async function advanceContiguousCursor(store, deps, bindingId) {
110
110
  if (!application || PENDING_STATES.has(application.state))
111
111
  break;
112
112
  }
113
- cursor = event.workspaceSequence;
113
+ cursor = event.projectSequence;
114
114
  }
115
- if (cursor > binding.lastAppliedWorkspaceSequence) {
116
- updateBindingCursor(store, bindingId, { lastAppliedWorkspaceSequence: cursor });
115
+ if (cursor > binding.lastAppliedProjectSequence) {
116
+ updateBindingCursor(store, bindingId, { lastAppliedProjectSequence: cursor });
117
117
  try {
118
118
  await acknowledgeCursor(deps.baseUrl, deps.accessToken, { bindingId, lastAppliedSequence: cursor });
119
119
  }
@@ -7,7 +7,7 @@ export function renderCommitNotification(application, changedPaths) {
7
7
  lines.push(` commit: ${application.generatedCommitId.slice(0, 12)}`);
8
8
  if (changedPaths.length > 0)
9
9
  lines.push(` changed: ${changedPaths.join(", ")}`);
10
- lines.push(` View: nustack workspace sync pending Undo: nustack workspace sync undo ${application.handoffId}`);
10
+ lines.push(` View: nustack project sync pending Undo: nustack project sync undo ${application.handoffId}`);
11
11
  if (application.recoveryBundleId) {
12
12
  lines.push(` Recovery bundle ${application.recoveryBundleId} holds your pre-approval content until it expires.`);
13
13
  }
@@ -43,11 +43,11 @@ function readBlob(repo, blobId) {
43
43
  export function isCommitTreeReadable(repo, commitId) {
44
44
  return runGit(repo.repositoryRoot, ["cat-file", "-e", `${commitId}^{tree}`], { allowFailure: true }).exitCode === 0;
45
45
  }
46
- export function extractCommitDelta(repo, commitId, whitelist, workspaceId, direction = "local_to_cloud") {
46
+ export function extractCommitDelta(repo, commitId, whitelist, projectId, direction = "local_to_cloud") {
47
47
  const parentCommitId = readRefCommit(repo, `${commitId}^1`);
48
- return computeTreeDelta(repo, parentCommitId, commitId, whitelist, workspaceId, direction);
48
+ return computeTreeDelta(repo, parentCommitId, commitId, whitelist, projectId, direction);
49
49
  }
50
- export function computeTreeDelta(repo, baseCommitId, targetCommitId, whitelist, workspaceId, direction = "local_to_cloud") {
50
+ export function computeTreeDelta(repo, baseCommitId, targetCommitId, whitelist, projectId, direction = "local_to_cloud") {
51
51
  const diffArgs = ["diff-tree", "-r", "-z", "--no-renames", "--no-commit-id"];
52
52
  const args = baseCommitId === null ? [...diffArgs, "--root", targetCommitId] : [...diffArgs, baseCommitId, targetCommitId];
53
53
  const { stdout } = runGit(repo.repositoryRoot, args);
@@ -81,7 +81,7 @@ export function computeTreeDelta(repo, baseCommitId, targetCommitId, whitelist,
81
81
  bytes: blob.bytes,
82
82
  byteSize: blob.byteSize,
83
83
  entry: match.entry,
84
- workspaceId,
84
+ projectId,
85
85
  });
86
86
  if (!validation.ok)
87
87
  return { ok: false, code: validation.code, path };