@north-light/api 0.3.457 → 0.3.459

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.
@@ -1,5 +1,5 @@
1
1
  /** The contract level this build serves and requires. Written by `scripts/contract.mjs release`; never edit by hand. */
2
- export declare const CONTRACT_LEVEL = "1.4";
2
+ export declare const CONTRACT_LEVEL = "1.5";
3
3
  /** Request header naming the one contract level the caller needs, e.g. `1.3`. Optional: absent means no check. */
4
4
  export declare const REQUIRED_CONTRACT_HEADER = "Crouter-Required-Contract";
5
5
  /** Response header listing the contract levels the server serves, comma-separated, one per major, e.g. `1.3`. */
@@ -12,7 +12,7 @@
12
12
  //
13
13
  // PURITY (spec §3.1): Node built-ins + `src/api/*` only.
14
14
  /** The contract level this build serves and requires. Written by `scripts/contract.mjs release`; never edit by hand. */
15
- export const CONTRACT_LEVEL = '1.4';
15
+ export const CONTRACT_LEVEL = '1.5';
16
16
  /** Request header naming the one contract level the caller needs, e.g. `1.3`. Optional: absent means no check. */
17
17
  export const REQUIRED_CONTRACT_HEADER = 'Crouter-Required-Contract';
18
18
  /** Response header listing the contract levels the server serves, comma-separated, one per major, e.g. `1.3`. */
@@ -112,6 +112,8 @@ export interface ProfileDTO {
112
112
  created_at: string;
113
113
  /** ISO timestamp of the most recent selection, or null if never selected. */
114
114
  last_used_at: string | null;
115
- /** Absolute path of the profile's directory under its app's space (the legacy location of its pre-canvas markdown; the profile's documents live in the canvas). */
115
+ /** Absolute path of the profile's directory, `<app space>/profiles/<id>`: its settings root. The profile's documents live in the canvas, not here. */
116
+ profile_path: string;
117
+ /** @deprecated The same path as `profile_path`; kept for clients written before it existed. Removed in the next breaking release. */
116
118
  memory_path: string;
117
119
  }
@@ -9,7 +9,7 @@ import { emitEvent } from './events/emit.js';
9
9
  import { atomicWriteJson, readJsonIfExists, ensureDir } from './fs-utils.js';
10
10
  import { scopeRoot, requireScopeRoot, findProjectScopeRoots, userScopeRoot } from './scope.js';
11
11
  import { listInstalledPluginsInRoot } from './installed-plugins.js';
12
- import { profileMemoryDir, listProfiles } from './profiles/manifest.js';
12
+ import { profileDir, listProfiles } from './profiles/manifest.js';
13
13
  import { canonicalRoot, projectStore, userStore } from './scoped-state/paths.js';
14
14
  import { convertLegacyScopedStore, initializeFreshScopedStore } from './scoped-state/migrate.js';
15
15
  import { mutateRawSettings, readBookkeeping, readRawSettings, readRawSettingsWithRevision, replaceRawSettings, writeBookkeeping, writeRawSettings } from './scoped-state/settings.js';
@@ -47,7 +47,7 @@ function settingsOwnerForRoot(root) {
47
47
  return { store: projectStore(canonical), key: 'project', kind: 'project', root: canonical };
48
48
  }
49
49
  for (const profile of listProfiles()) {
50
- const profilePath = canonicalRoot(profileMemoryDir(profile.profileId));
50
+ const profilePath = canonicalRoot(profileDir(profile.profileId));
51
51
  if (canonical === profilePath)
52
52
  return { store: userStore(userRoot), key: `profile:${profile.profileId}`, kind: 'profile', root: profilePath, profileId: profile.profileId };
53
53
  }
@@ -129,7 +129,7 @@ function rawSettingsOwnerForTarget(target) {
129
129
  if (!listProfiles().some((profile) => profile.profileId === target.profileId)) {
130
130
  throw notFound(`profile not found: ${target.profileId}`, { received: target.profileId, next: 'Run `crtr profile list` to see available profiles.' });
131
131
  }
132
- return settingsOwnerForRoot(profileMemoryDir(target.profileId));
132
+ return settingsOwnerForRoot(profileDir(target.profileId));
133
133
  }
134
134
  export function readRawSettingsForTarget(target) {
135
135
  return readRawSettingsWithRevision(rawSettingsOwnerForTarget(target));
@@ -877,7 +877,7 @@ function readRawProfileConfig(profileId) {
877
877
  if (profileId === null || profileId === '')
878
878
  return null;
879
879
  try {
880
- return readRawAtRoot(profileMemoryDir(profileId));
880
+ return readRawAtRoot(profileDir(profileId));
881
881
  }
882
882
  catch {
883
883
  return null;
@@ -6,7 +6,7 @@ export declare function userRoot(): string;
6
6
  export declare function appsRoot(): string;
7
7
  export declare function secretsDir(): string;
8
8
  export declare function profileSpace(app: string, id: string): {
9
- memory: string;
9
+ dir: string;
10
10
  env: string;
11
11
  home: string;
12
12
  };
@@ -15,7 +15,7 @@ export declare function layoutVersionFile(): string;
15
15
  export declare function userFilesRoot(): string;
16
16
  export declare function appSpace(app: string): {
17
17
  root: string;
18
- memory: string;
18
+ profiles: string;
19
19
  conversations: string;
20
20
  nodes: string;
21
21
  files: string;
@@ -44,7 +44,7 @@ export function profileSpace(app, id) {
44
44
  throw new Error(`invalid profile id: ${id}`);
45
45
  const space = appSpace(app);
46
46
  return {
47
- memory: join(space.memory, 'profiles', id),
47
+ dir: join(space.profiles, id),
48
48
  env: join(secretsDir(), 'env', basename(space.root), id),
49
49
  home: space.home,
50
50
  };
@@ -62,7 +62,7 @@ export function appSpace(app) {
62
62
  if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(id) || id === '.' || id === '..')
63
63
  throw new Error(`invalid app id: ${app}`);
64
64
  const root = join(appsRoot(), id);
65
- return { root, memory: join(root, 'memory'), conversations: join(root, 'conversations'), nodes: join(root, 'nodes'), files: join(root, 'files'), home: join(root, 'home'), cache: join(root, 'cache') };
65
+ return { root, profiles: join(root, 'profiles'), conversations: join(root, 'conversations'), nodes: join(root, 'nodes'), files: join(root, 'files'), home: join(root, 'home'), cache: join(root, 'cache') };
66
66
  }
67
67
  /** The folder an isolated cron runs in: `<app space>/crons/<cron_id>/`. It belongs to the cron, not to the node that armed it. */
68
68
  export function cronFolder(app, cronId) {
@@ -1,7 +1,7 @@
1
1
  import type { ProfileManifest } from '../../types.js';
2
2
  import type { DeliveryLimits, ProfileProject, ProfileProjectMemory } from '../../api/dto/profiles.js';
3
3
  export declare const ROOT_PROFILE_ID = "root-00000000";
4
- export declare function profileMemoryDir(profileId: string): string;
4
+ export declare function profileDir(profileId: string): string;
5
5
  export declare function assertProfileProjects(entries: unknown): asserts entries is ProfileProject[];
6
6
  export declare function assertDeliveryLimits(value: unknown): asserts value is DeliveryLimits;
7
7
  export interface ProfileEntry {
@@ -21,7 +21,7 @@ export const ROOT_PROFILE_ID = 'root-00000000';
21
21
  const ROOT_PROFILE_NAME = 'root';
22
22
  function profileApp(manifest) { return manifest.grantee ?? 'app:terminal'; }
23
23
  function scaffoldProfile(profileId, manifest) {
24
- ensureDir(profileSpace(profileApp(manifest), profileId).memory);
24
+ ensureDir(profileSpace(profileApp(manifest), profileId).dir);
25
25
  }
26
26
  function assertSafeProfileId(profileId) {
27
27
  if (!ID_SHAPE.test(profileId)) {
@@ -31,12 +31,12 @@ function assertSafeProfileId(profileId) {
31
31
  });
32
32
  }
33
33
  }
34
- export function profileMemoryDir(profileId) {
34
+ export function profileDir(profileId) {
35
35
  assertSafeProfileId(profileId);
36
36
  const stored = getStoredProfile(userScopeRoot(), profileId);
37
37
  if (stored === null)
38
38
  throw notFound(`profile not found: ${profileId}`, { received: profileId });
39
- return profileSpace(profileApp(stored.manifest), profileId).memory;
39
+ return profileSpace(profileApp(stored.manifest), profileId).dir;
40
40
  }
41
41
  // Profile operands resolve against the scoped-state rows.
42
42
  export function assertProfileProjects(entries) {
@@ -523,7 +523,7 @@ export function deleteProfile(profileId) {
523
523
  const space = profileSpace(profileApp(loadExactProfileManifest(profileId).manifest), profileId);
524
524
  if (!deleteStoredProfile(userScopeRoot(), profileId))
525
525
  throw notFound(`profile not found: ${profileId}`, { received: profileId });
526
- rmSync(space.memory, { recursive: true, force: true });
526
+ rmSync(space.dir, { recursive: true, force: true });
527
527
  rmSync(space.env, { force: true });
528
528
  });
529
529
  }
@@ -164,7 +164,7 @@ function importUser(db, root) {
164
164
  if (!manifest || typeof manifest !== 'object' || !Array.isArray(manifest.projects) || typeof manifest.name !== 'string' || typeof manifest.created_at !== 'string')
165
165
  throw new Error(`legacy profile manifest is malformed: ${path}`);
166
166
  cachedStatement(db, 'INSERT INTO profiles(id,name,created_at,last_used_at,paused_at,default_kind,grantee,metadata_json) VALUES(?,?,?,?,?,?,?,?)').run(id, manifest.name, manifest.created_at, manifest.last_used_at ?? null, manifest.paused_at ?? null, manifest.default_kind ?? null, manifest.grantee ?? null, JSON.stringify(manifest.metadata ?? {}));
167
- cachedStatement(db, 'INSERT INTO scope_owners(owner_key,kind,root,profile_id) VALUES(?,?,?,?)').run(`profile:${id}`, 'profile', canonicalRoot(profileSpace(manifest.grantee ?? 'app:terminal', id).memory), id);
167
+ cachedStatement(db, 'INSERT INTO scope_owners(owner_key,kind,root,profile_id) VALUES(?,?,?,?)').run(`profile:${id}`, 'profile', canonicalRoot(profileSpace(manifest.grantee ?? 'app:terminal', id).dir), id);
168
168
  const project = cachedStatement(db, 'INSERT INTO profile_projects(profile_id,path,position) VALUES(?,?,?)');
169
169
  const cap = cachedStatement(db, 'INSERT INTO legacy_project_caps(profile_id,path,memory) VALUES(?,?,?)');
170
170
  manifest.projects.forEach((p, position) => { project.run(id, p.path, position); if (p.memory !== undefined && p.memory !== 'content')
@@ -31,7 +31,7 @@ export function pausedProfileIds(root) {
31
31
  export function insertStoredProfile(root, profile) {
32
32
  withScopedWrite(store(root), (db) => {
33
33
  cachedStatement(db, 'INSERT INTO profiles(id,name,created_at,last_used_at,paused_at,default_kind,grantee,metadata_json,delivery_limits_json) VALUES(?,?,?,?,?,?,?,?,?)').run(profile.profileId, profile.manifest.name, profile.manifest.created_at, profile.manifest.last_used_at, profile.manifest.paused_at, profile.manifest.default_kind ?? null, profile.manifest.grantee ?? null, JSON.stringify(profile.manifest.metadata ?? {}), JSON.stringify(profile.manifest.delivery_limits ?? {}));
34
- cachedStatement(db, 'INSERT INTO scope_owners(owner_key,kind,root,profile_id) VALUES(?,?,?,?)').run(`profile:${profile.profileId}`, 'profile', canonicalRoot(profileSpace(profile.manifest.grantee ?? 'app:terminal', profile.profileId).memory), profile.profileId);
34
+ cachedStatement(db, 'INSERT INTO scope_owners(owner_key,kind,root,profile_id) VALUES(?,?,?,?)').run(`profile:${profile.profileId}`, 'profile', canonicalRoot(profileSpace(profile.manifest.grantee ?? 'app:terminal', profile.profileId).dir), profile.profileId);
35
35
  const insert = cachedStatement(db, 'INSERT INTO profile_projects(profile_id,path,position) VALUES(?,?,?)');
36
36
  profile.manifest.projects.forEach((project, position) => insert.run(profile.profileId, project.path, position));
37
37
  });
@@ -21,7 +21,10 @@ function ensureOwner(db, owner, access) {
21
21
  }
22
22
  if (row.root === owner.root)
23
23
  return;
24
- if (owner.kind !== 'project' || owner.store.kind !== 'project' || owner.store.root !== owner.root) {
24
+ // A profile row is keyed by its id, checked above; its recorded root is a note of where the folder was. A profile
25
+ // folder that moved (`<app>/memory/profiles/<id>` → `<app>/profiles/<id>`) is rebound by the first write.
26
+ const profileMoved = owner.kind === 'profile' && owner.profileId !== undefined;
27
+ if (!profileMoved && (owner.kind !== 'project' || owner.store.kind !== 'project' || owner.store.root !== owner.root)) {
25
28
  throw new Error(`scoped SQL owner does not match its root: ${owner.key}`);
26
29
  }
27
30
  if (access === 'write')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/api",
3
- "version": "0.3.457",
3
+ "version": "0.3.459",
4
4
  "description": "Typed crtrd /v1 API contract — DTOs, route builders, the error contract, the CrtrClient, and the command-plugin manifest format. Zero runtime dependencies.",
5
5
  "type": "module",
6
6
  "main": "./dist/api/index.js",