@ours.network/install 1.2.0-nightly.2 → 1.2.1-nightly.1

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.
@@ -0,0 +1,43 @@
1
+ {
2
+ "schema": 1,
3
+ "channel": "nightly",
4
+ "installerVersion": "1.2.1-nightly.1",
5
+ "packages": {
6
+ "@ours.network/sdk": {
7
+ "version": "3.8.1-nightly.3",
8
+ "integrity": "sha512-iUVCs+jryoeR2Ip/uHzpkJwbpoJeLDASUaE3lC+eYDj8dbI1MBwTpeOXFemUK052O6lA4ZWrhyRgE9pJU3a+Kw=="
9
+ },
10
+ "@ours.network/cli": {
11
+ "version": "2.8.1-nightly.1",
12
+ "integrity": "sha512-1Rmbqlr790FhqQW/wQ//YEL7b0Q2KVSCA2TlpjaLRkj9Xh55rOoazBJjFuW/fUpTz3Z1B3MUPK9LWiYd+A0kNA=="
13
+ },
14
+ "@ours.network/tg-connector": {
15
+ "version": "1.0.1-nightly.1",
16
+ "integrity": "sha512-/Do7EfINknDks3yYndOi9aLc1alxB50qk+WKv8DRuZpDB89DDMQ+DoSGQerdvsMp32nQ3LICL8TPjcdFMZfx8Q=="
17
+ },
18
+ "@ours.network/cowork": {
19
+ "version": "1.3.2-nightly.20260918.4d9242e",
20
+ "integrity": "sha512-04D2lJ97SrTrkVRRbdTZ0y+KvQdG/bpnmFFjsyUqQTij+5zEkNizTG/xhcCL8m7Mb0XvVFL8WzJ62Xgh5ahlRw=="
21
+ },
22
+ "@ours.network/messenger-server": {
23
+ "version": "1.0.31-nightly.1",
24
+ "integrity": "sha512-XB6N+Ppd4txmI0+2ZVFMWJxXi4DtMIDlFSHOzszNM5hopFebpw1Df+KKCK4Cgd/LZUkoOOYWGEYpib9m3RbHWw=="
25
+ },
26
+ "@ours.network/fleet": {
27
+ "version": "1.2.0-nightly.1",
28
+ "integrity": "sha512-vioCobVbosmp9BgYo2XccjpPoVQd1wwV+Mt64Bm41/Vxtro2j85PUdy4AWcsY9jEFnJmj/UQfc5E5AxvlpPnrw=="
29
+ },
30
+ "@ours.network/mcp": {
31
+ "version": "1.2.0-nightly.3",
32
+ "integrity": "sha512-z0N5jn1jj9iwGRqU1d3AM2RX9mCr4T/WhASEKr+Vh52yNDMzhVjFlx0U0v8aS+GTfOQaPZTfL3TGMc17ETjh3g=="
33
+ },
34
+ "@ours.network/codex": {
35
+ "version": "1.2.0-nightly.3",
36
+ "integrity": "sha512-dIWDqbo6hJzujegHRO6x1SsBnh5p1c6gRGj/WkPha/hcN7s/27P4jqBnJ7czQyVGiNVFZ1aAxj4c9+CDLzHENw=="
37
+ },
38
+ "@ours.network/claude-code": {
39
+ "version": "1.2.0-nightly.3",
40
+ "integrity": "sha512-7ta6KnvoHB2i/yoaykhXvzKFQTIOPtDHeTzG1PRwHjdTJ4vcGEh4jWYMd2c1jjj73lC3q9qW3LYSRutIJovtQg=="
41
+ }
42
+ }
43
+ }
@@ -4,6 +4,7 @@ import { join, resolve } from 'node:path';
4
4
  import { pathToFileURL } from 'node:url';
5
5
  import { execFileSync } from 'node:child_process';
6
6
  import { createBuildContext, CONTEXT } from '../maintenance/build-context.mjs';
7
+ import { verifyRuntimeRelease } from '../maintenance/release-graph.mjs';
7
8
  export function finalizeBuild(root) {
8
9
  if (fs.realpathSync(root) !== root) throw new Error('Build root must be canonical');
9
10
  try { fs.lstatSync(join(root, CONTEXT)); throw new Error('Existing context cannot be regenerated'); }
@@ -20,6 +21,7 @@ export function finalizeBuild(root) {
20
21
  if (typeof spec !== 'string' || !/^file:docker\/vendor\/ours\.network-[a-z-]+\.tgz$/.test(spec)) throw new Error('Unexpected installer vendor reference');
21
22
  protect(join(root, spec.slice(5)));
22
23
  }
24
+ verifyRuntimeRelease(root);
23
25
  const tree = execFileSync('npm', ['ls', '--omit=dev', '--all', '--json'], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
24
26
  JSON.parse(tree);
25
27
  const path = join(root, 'dependency-tree.json');
@@ -0,0 +1,94 @@
1
+ /** Enforce the selected ours artifacts; this is not third-party lock replay. */
2
+ import { existsSync, lstatSync, readFileSync, realpathSync } from 'node:fs';
3
+ import { join, resolve, relative, isAbsolute } from 'node:path';
4
+ import { createHash } from 'node:crypto';
5
+
6
+ const names = ['sdk', 'cli', 'tg-connector', 'cowork', 'messenger-server', 'fleet', 'mcp', 'codex', 'claude-code'].map(name => '@ours.network/' + name);
7
+ const stable = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
8
+ const nightly = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)-nightly\.(0|[1-9]\d*)(?:\.[0-9a-f]{7,40})?$/;
9
+ const fail = message => { throw new Error(`Release graph refused: ${message}`); };
10
+ function file(path) {
11
+ if (!lstatSync(path).isFile() || realpathSync(path) !== path) fail(`non-regular or linked file: ${path}`);
12
+ return readFileSync(path);
13
+ }
14
+ const json = path => JSON.parse(file(path));
15
+
16
+ export function releaseBinding(policy) {
17
+ // Only an absent binding denotes explicit development/retained legacy inputs.
18
+ if (!Object.hasOwn(policy ?? {}, 'release')) return null;
19
+ const release = policy.release;
20
+ if (release?.schema !== 1 || !['stable', 'nightly'].includes(release.channel)) fail('invalid release binding');
21
+ const pattern = release.channel === 'nightly' ? nightly : stable;
22
+ if (typeof release.installerVersion !== 'string' || !pattern.test(release.installerVersion)) fail('invalid installer version/channel');
23
+ if (!release.packages || JSON.stringify(Object.keys(release.packages).sort()) !== JSON.stringify([...names].sort())) fail('release must select exactly nine ours packages');
24
+ for (const [name, entry] of Object.entries(release.packages)) {
25
+ if (typeof entry?.version !== 'string' || !pattern.test(entry.version) || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(entry.integrity ?? '')) fail(`invalid release artifact: ${name}`);
26
+ }
27
+ if (!policy.packages || !Object.keys(policy.packages).length) fail('missing release source selection');
28
+ for (const [name, entry] of Object.entries(policy.packages)) {
29
+ if (entry?.type !== 'npm' || entry.version !== release.packages[name]?.version || Object.keys(entry).sort().join(',') !== 'type,version') fail(`source selection differs from release: ${name}`);
30
+ }
31
+ return release;
32
+ }
33
+
34
+ export function verifyReleaseGraph(root, policy, { requiredPackages = Object.keys(policy?.packages ?? {}) } = {}) {
35
+ const release = releaseBinding(policy);
36
+ if (!release) return { verified: false, reason: 'development or retained legacy source selection' };
37
+ root = resolve(root);
38
+ if (realpathSync(root) !== root) fail('build root is not canonical');
39
+ const manifest = json(join(root, 'package.json'));
40
+ if (manifest.name?.startsWith('@ours.network/') && manifest.version !== release.packages[manifest.name]?.version) fail(`root package differs from release: ${manifest.name}`);
41
+ const locks = ['package-lock.json', 'node_modules/.package-lock.json'].map(path => {
42
+ const lock = json(join(root, path));
43
+ if (lock.lockfileVersion !== 3 || !lock.packages || Array.isArray(lock.packages)) fail(`invalid lock: ${path}`);
44
+ const entries = new Map();
45
+ for (const [location, entry] of Object.entries(lock.packages)) {
46
+ const match = location.match(/(?:^|\/)node_modules\/(@ours\.network\/[^/]+)$/);
47
+ if (!match) continue;
48
+ const name = match[1], expected = release.packages[name];
49
+ if (!expected || entry?.link || entry.version !== expected.version || entry.integrity !== expected.integrity) fail(`version/integrity mismatch: ${path}:${location}`);
50
+ const installed = resolve(root, location), rel = relative(root, installed);
51
+ if (!rel || rel.startsWith('..') || isAbsolute(rel)) fail(`escaping lock location: ${location}`);
52
+ if (typeof entry.resolved !== 'string') fail(`missing artifact location: ${location}`);
53
+ if (entry.resolved.startsWith('file:')) {
54
+ const archive = resolve(root, entry.resolved.slice(5));
55
+ const archiveRel = relative(root, archive);
56
+ if (archiveRel.startsWith('..') || isAbsolute(archiveRel) || !archive.endsWith('.tgz')) fail(`unbound local artifact: ${location}`);
57
+ const integrity = 'sha512-' + createHash('sha512').update(file(archive)).digest('base64');
58
+ if (integrity !== expected.integrity) fail(`vendor bytes differ from release: ${location}`);
59
+ } else if (!entry.resolved.startsWith(`https://registry.npmjs.org/${name}/-/`)) fail(`non-official artifact: ${location}`);
60
+ entries.set(location, { name, entry, installed });
61
+ }
62
+ return entries;
63
+ });
64
+ const [declared, installed] = locks;
65
+ const direct = Object.entries(manifest.dependencies ?? {}).filter(([name]) => name.startsWith('@ours.network/'));
66
+ if (!manifest.name?.startsWith('@ours.network/') && JSON.stringify(direct.map(([name]) => name).sort()) !== JSON.stringify([...requiredPackages].sort())) fail('direct package set differs from release source selection');
67
+ for (const [name, spec] of direct) {
68
+ const expected = release.packages[name], locked = declared.get(`node_modules/${name}`)?.entry;
69
+ if (!expected || (spec !== expected.version && !(typeof spec === 'string' && spec.startsWith('file:') && spec === locked?.resolved))) fail(`direct package spec differs from release: ${name}`);
70
+ }
71
+ for (const name of Object.keys(manifest.dependencies ?? {}).filter(name => name.startsWith('@ours.network/'))) {
72
+ if (!declared.has(`node_modules/${name}`) || !installed.has(`node_modules/${name}`)) fail(`missing direct release package: ${name}`);
73
+ }
74
+ for (const [location, value] of declared) {
75
+ // npm's root lock retains dev-only entries omitted from a production install.
76
+ if (!installed.has(location) && !value.entry.dev && !value.entry.optional) fail(`installed release entry missing: ${location}`);
77
+ }
78
+ for (const [location, value] of installed) {
79
+ const expected = declared.get(location);
80
+ if (!expected || expected.entry.version !== value.entry.version || expected.entry.integrity !== value.entry.integrity || expected.entry.resolved !== value.entry.resolved) fail(`installed/root lock mismatch: ${location}`);
81
+ if (!lstatSync(value.installed).isDirectory() || realpathSync(value.installed) !== value.installed) fail(`linked installed package: ${location}`);
82
+ const actual = json(join(value.installed, 'package.json'));
83
+ if (actual.name !== value.name || actual.version !== value.entry.version) fail(`installed package differs from release: ${location}`);
84
+ }
85
+ return { verified: true, packages: installed.size };
86
+ }
87
+
88
+ export function verifyRuntimeRelease(root) {
89
+ const path = join(resolve(root), 'sources.json');
90
+ // Historic installations predate sources/release bindings; their existing
91
+ // provenance and conversion checks remain authoritative until explicit update.
92
+ if (!existsSync(path)) return { verified: false, reason: 'legacy runtime without sources' };
93
+ return verifyReleaseGraph(root, json(path));
94
+ }
@@ -1,4 +1,5 @@
1
1
  import { readBuildRecords, initializeBuildMarker } from '../maintenance/build-context.mjs';
2
+ import { verifyRuntimeRelease } from '../maintenance/release-graph.mjs';
2
3
  import { accessSync, constants, lstatSync, readFileSync } from 'node:fs';
3
4
 
4
5
  export function privatePath(path, directory = false, writable = false) {
@@ -43,5 +44,6 @@ export function checkCredential(path) {
43
44
  // Called with the startup state-directory lock held. These are build records,
44
45
  // not a storage schema or a declaration that arbitrary upgrades are compatible.
45
46
  export function recordBuild(state) {
47
+ verifyRuntimeRelease('/opt/ours');
46
48
  initializeBuildMarker(`${state}/.ours-provenance`, readBuildRecords('/opt/ours'));
47
49
  }
@@ -1,21 +1,83 @@
1
1
  {
2
- "sources": {
3
- "sdk": { "type": "git", "url": "https://github.com/adapt-toolkit/ours-sdk.git", "commit": "9cb51add34888e6ae2cc3b554c97362bb1ca2d33" },
4
- "telegram": { "type": "git", "url": "https://github.com/adapt-toolkit/ours-tg-connector.git", "commit": "81798b836b15ac4ba9b32ac180f94fb258a54a41" },
5
- "cowork": { "type": "git", "url": "https://github.com/adapt-toolkit/ours-cowork.git", "commit": "1e17f7666c1682a858101c98a8f371aac7b737e3" },
6
- "messenger": { "type": "git", "url": "https://github.com/adapt-toolkit/ours-messenger-server.git", "commit": "fc852e49eb3b43c077f59cf4919536be6f6d03b1" },
7
- "fleet": { "type": "git", "url": "https://github.com/adapt-toolkit/ours-fleet.git", "commit": "b8444a7aac2eb6148a3ab534e90308bfc76447d7" },
8
- "mcp": { "type": "git", "url": "https://github.com/adapt-toolkit/ours-mcp.git", "commit": "3d970d0cb7baf52351d5cd44dd59d4a2f705207b" }
2
+ "release": {
3
+ "schema": 1,
4
+ "channel": "nightly",
5
+ "installerVersion": "1.2.1-nightly.1",
6
+ "packages": {
7
+ "@ours.network/sdk": {
8
+ "version": "3.8.1-nightly.3",
9
+ "integrity": "sha512-iUVCs+jryoeR2Ip/uHzpkJwbpoJeLDASUaE3lC+eYDj8dbI1MBwTpeOXFemUK052O6lA4ZWrhyRgE9pJU3a+Kw=="
10
+ },
11
+ "@ours.network/cli": {
12
+ "version": "2.8.1-nightly.1",
13
+ "integrity": "sha512-1Rmbqlr790FhqQW/wQ//YEL7b0Q2KVSCA2TlpjaLRkj9Xh55rOoazBJjFuW/fUpTz3Z1B3MUPK9LWiYd+A0kNA=="
14
+ },
15
+ "@ours.network/tg-connector": {
16
+ "version": "1.0.1-nightly.1",
17
+ "integrity": "sha512-/Do7EfINknDks3yYndOi9aLc1alxB50qk+WKv8DRuZpDB89DDMQ+DoSGQerdvsMp32nQ3LICL8TPjcdFMZfx8Q=="
18
+ },
19
+ "@ours.network/cowork": {
20
+ "version": "1.3.2-nightly.20260918.4d9242e",
21
+ "integrity": "sha512-04D2lJ97SrTrkVRRbdTZ0y+KvQdG/bpnmFFjsyUqQTij+5zEkNizTG/xhcCL8m7Mb0XvVFL8WzJ62Xgh5ahlRw=="
22
+ },
23
+ "@ours.network/messenger-server": {
24
+ "version": "1.0.31-nightly.1",
25
+ "integrity": "sha512-XB6N+Ppd4txmI0+2ZVFMWJxXi4DtMIDlFSHOzszNM5hopFebpw1Df+KKCK4Cgd/LZUkoOOYWGEYpib9m3RbHWw=="
26
+ },
27
+ "@ours.network/fleet": {
28
+ "version": "1.2.0-nightly.1",
29
+ "integrity": "sha512-vioCobVbosmp9BgYo2XccjpPoVQd1wwV+Mt64Bm41/Vxtro2j85PUdy4AWcsY9jEFnJmj/UQfc5E5AxvlpPnrw=="
30
+ },
31
+ "@ours.network/mcp": {
32
+ "version": "1.2.0-nightly.3",
33
+ "integrity": "sha512-z0N5jn1jj9iwGRqU1d3AM2RX9mCr4T/WhASEKr+Vh52yNDMzhVjFlx0U0v8aS+GTfOQaPZTfL3TGMc17ETjh3g=="
34
+ },
35
+ "@ours.network/codex": {
36
+ "version": "1.2.0-nightly.3",
37
+ "integrity": "sha512-dIWDqbo6hJzujegHRO6x1SsBnh5p1c6gRGj/WkPha/hcN7s/27P4jqBnJ7czQyVGiNVFZ1aAxj4c9+CDLzHENw=="
38
+ },
39
+ "@ours.network/claude-code": {
40
+ "version": "1.2.0-nightly.3",
41
+ "integrity": "sha512-7ta6KnvoHB2i/yoaykhXvzKFQTIOPtDHeTzG1PRwHjdTJ4vcGEh4jWYMd2c1jjj73lC3q9qW3LYSRutIJovtQg=="
42
+ }
43
+ }
9
44
  },
10
45
  "packages": {
11
- "@ours.network/sdk": { "source": "sdk" },
12
- "@ours.network/cli": { "source": "sdk" },
13
- "@ours.network/tg-connector": { "source": "telegram" },
14
- "@ours.network/cowork": { "source": "cowork" },
15
- "@ours.network/messenger-server": { "source": "messenger" },
16
- "@ours.network/fleet": { "source": "fleet" },
17
- "@ours.network/mcp": { "source": "mcp" },
18
- "@ours.network/codex": { "source": "mcp" },
19
- "@ours.network/claude-code": { "source": "mcp" }
46
+ "@ours.network/sdk": {
47
+ "type": "npm",
48
+ "version": "3.8.1-nightly.3"
49
+ },
50
+ "@ours.network/cli": {
51
+ "type": "npm",
52
+ "version": "2.8.1-nightly.1"
53
+ },
54
+ "@ours.network/tg-connector": {
55
+ "type": "npm",
56
+ "version": "1.0.1-nightly.1"
57
+ },
58
+ "@ours.network/cowork": {
59
+ "type": "npm",
60
+ "version": "1.3.2-nightly.20260918.4d9242e"
61
+ },
62
+ "@ours.network/messenger-server": {
63
+ "type": "npm",
64
+ "version": "1.0.31-nightly.1"
65
+ },
66
+ "@ours.network/fleet": {
67
+ "type": "npm",
68
+ "version": "1.2.0-nightly.1"
69
+ },
70
+ "@ours.network/mcp": {
71
+ "type": "npm",
72
+ "version": "1.2.0-nightly.3"
73
+ },
74
+ "@ours.network/codex": {
75
+ "type": "npm",
76
+ "version": "1.2.0-nightly.3"
77
+ },
78
+ "@ours.network/claude-code": {
79
+ "type": "npm",
80
+ "version": "1.2.0-nightly.3"
81
+ }
20
82
  }
21
83
  }
package/install.sh CHANGED
@@ -9,7 +9,7 @@
9
9
  # pipes a script into your shell). It just gets Node.js/npm sorted, then does the same
10
10
  # `npm i -g @ours.network/install` and runs `ours-install`. Meant to be run as:
11
11
  #
12
- # curl -fsSL https://raw.githubusercontent.com/adapt-toolkit/ours-mcp/main/packages/installer/install.sh | bash
12
+ # curl -fsSL https://raw.githubusercontent.com/adapt-toolkit/ours-network/main/install.sh | bash
13
13
  #
14
14
  # This file is a THIN bootstrap: it checks that Node.js + npm are present (and prints friendly,
15
15
  # per-OS guidance if not), then runs the real experience — the Node installer (install.mjs) that
@@ -22,6 +22,7 @@ export async function prepareDockerConversionRuntime(record, effects, assets) {
22
22
  fs.writeFileSync(join(context, 'Dockerfile'), `# syntax=docker/dockerfile:1
23
23
  FROM ${record.project}:runtime AS runtime
24
24
  COPY --chmod=644 runtime/ /opt/ours/docker/
25
+ COPY --chmod=644 maintenance/release-graph.mjs /opt/ours/maintenance/release-graph.mjs
25
26
  FROM runtime AS maintenance
26
27
  USER 0:0
27
28
  COPY --chmod=644 maintenance/ /opt/ours/docker/
package/lib/effects.mjs CHANGED
@@ -24,6 +24,7 @@ import { askYesNo, askLine as askLineOnTty } from './prompt.mjs';
24
24
  import { classifyHarnessProbe } from './logic.mjs';
25
25
  import { classifyStateDir } from './detect.mjs';
26
26
  import { BASE_RECORDS, CONTEXT, readBuildRecords, equalBuildRecords, initializeBuildMarker } from '../assets/scripts/maintenance/build-context.mjs';
27
+ import { releaseBinding, verifyReleaseGraph, verifyRuntimeRelease } from '../assets/scripts/maintenance/release-graph.mjs';
27
28
 
28
29
  /** GET http://127.0.0.1:<port>/state-dir — the unauthenticated identity probe. */
29
30
  async function probePort(port, { timeoutMs = 1500 } = {}) {
@@ -569,7 +570,15 @@ export function networkEffects(effects) {
569
570
  return createHash('sha256').update(readFileSync(path)).digest('hex');
570
571
  },
571
572
  packagedSourcePolicy() {
572
- return JSON.parse(readFileSync(PACKAGED_SOURCE_POLICY, 'utf8'));
573
+ const policy = JSON.parse(readFileSync(PACKAGED_SOURCE_POLICY, 'utf8'));
574
+ const release = releaseBinding(policy);
575
+ if (release) {
576
+ const embedded = JSON.parse(readFileSync(join(INSTALLER_ASSETS, 'release.json'), 'utf8'));
577
+ if (JSON.stringify(release) !== JSON.stringify(embedded)) throw new Error('Packaged source policy differs from immutable release');
578
+ } else if (Object.values(policy.packages ?? {}).some(p => p.type === 'npm')) {
579
+ throw new Error('Packaged npm source policy is missing its release binding');
580
+ }
581
+ return policy;
573
582
  },
574
583
  async resolveSourcePolicy(policy, role, clients = []) {
575
584
  return resolveSourcePolicy(policy, role, clients, async (name, range) => {
@@ -867,6 +876,7 @@ export function networkEffects(effects) {
867
876
  },
868
877
  async recordRuntimeBuild(record) {
869
878
  if (record.mode === 'docker') return; // Image preparation records its build.
879
+ verifyRuntimeRelease(record.workDir);
870
880
  const tree = join(record.workDir, 'dependency-tree.json');
871
881
  if (!existsSync(tree)) {
872
882
  const result = await effects.run('npm', ['ls', '--omit=dev', '--all', '--json'], { cwd: record.workDir });
@@ -1143,8 +1153,10 @@ export function networkEffects(effects) {
1143
1153
  writeFileSync(join(root, 'package.json'), JSON.stringify({ name: 'ours-native-clients', private: true, dependencies: Object.fromEntries(Object.entries(packages).map(([name, selection]) => [name, selection.version])) }), { mode: 0o600 });
1144
1154
  }
1145
1155
  await effects.run('npm', ['install', '--omit=dev', '--no-audit', '--no-fund'], { cwd: root });
1156
+ verifyReleaseGraph(root, manifest, { requiredPackages: Object.keys(packages) });
1146
1157
  writePrivateNew(join(root, '.packages-ready'), 'ready\n');
1147
1158
  }
1159
+ verifyReleaseGraph(root, manifest, { requiredPackages: Object.keys(packages) });
1148
1160
  // Local acquisition alone does not publish native commands. Use the user's
1149
1161
  // configured npm prefix and retained dependency closure, including on retry.
1150
1162
  for (const name of integrations.filter(name => name === 'fleet' || name === 'codex')) {
@@ -1154,22 +1166,34 @@ export function networkEffects(effects) {
1154
1166
  return { localPackages, packages: {}, fleetBin: integrations.includes('fleet') ? join(root, 'node_modules/.bin/ours-fleet') : null };
1155
1167
  },
1156
1168
  async prepareClientMarketplace(name, packagePath) {
1157
- const root = join(dirname(dirname(dirname(packagePath))), 'marketplaces', name);
1169
+ const acquisitionRoot = dirname(dirname(dirname(packagePath)));
1170
+ const sourcePath = join(acquisitionRoot, 'sources.json');
1171
+ const policy = existsSync(sourcePath) ? JSON.parse(readFileSync(sourcePath, 'utf8')) : {}; // Retained pre-release client acquisitions.
1172
+ const release = releaseBinding(policy);
1173
+ const integrationsPath = join(acquisitionRoot, 'integrations.json');
1174
+ const integrations = existsSync(integrationsPath) ? JSON.parse(readFileSync(integrationsPath, 'utf8')) : null;
1175
+ const requiredPackages = integrations ? [...new Set(['sdk', ...(integrations.includes('fleet') ? ['cli'] : []), ...integrations])].map(name => '@ours.network/' + name) : Object.keys(policy.packages ?? {});
1176
+ verifyReleaseGraph(acquisitionRoot, policy, { requiredPackages });
1177
+ const root = join(acquisitionRoot, 'marketplaces', name);
1158
1178
  const plugin = join(root, 'plugins', 'ours');
1159
1179
  if (!existsSync(plugin)) {
1160
1180
  mkdirSync(dirname(plugin), { recursive: true, mode: 0o700 });
1161
1181
  cpSync(packagePath, plugin, { recursive: true });
1162
1182
  const manifestPath = join(plugin, 'package.json');
1163
1183
  const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
1164
- for (const dependency of ['sdk', 'cli']) {
1184
+ for (const dependency of release ? [] : ['sdk', 'cli']) {
1165
1185
  const name = `@ours.network/${dependency}`;
1166
1186
  if (manifest.dependencies?.[name]) manifest.dependencies[name] = `file:${join(dirname(packagePath), dependency)}`;
1167
1187
  }
1168
1188
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
1169
1189
  }
1190
+ if (release && JSON.stringify(JSON.parse(readFileSync(join(plugin, 'package.json'), 'utf8'))) !== JSON.stringify(JSON.parse(readFileSync(join(packagePath, 'package.json'), 'utf8')))) {
1191
+ throw new Error('Marketplace package differs from verified release acquisition');
1192
+ }
1170
1193
  // Native caches copy plugin contents; local SDK/CLI dependencies must not
1171
1194
  // remain links to acquisition paths. Repeating setup also repairs an interrupted install.
1172
1195
  await effects.run('npm', ['install', '--install-links', '--omit=dev', '--ignore-scripts', '--no-audit', '--no-fund'], { cwd: plugin });
1196
+ verifyReleaseGraph(plugin, policy);
1173
1197
  const value = name === 'codex'
1174
1198
  ? { name: 'ours-codex-marketplace', plugins: [{ name: 'ours', source: { source: 'local', path: './plugins/ours' }, policy: { installation: 'AVAILABLE', authentication: 'ON_INSTALL' }, category: 'Productivity' }] }
1175
1199
  : { name: 'ours.network', owner: { name: 'Adapt Toolkit' }, plugins: [{ name: 'ours', source: './plugins/ours' }] };
package/lib/plan.mjs CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { join, resolve, basename, dirname } from 'node:path';
8
8
  import { valid, validRange, satisfies } from 'semver';
9
+ import { releaseBinding } from '../assets/scripts/maintenance/release-graph.mjs';
9
10
 
10
11
  export const CLI_UNIT_MARKER = '# Managed by @ours.network/cli';
11
12
  export const SYSTEMD_USER_DIR = ['.config', 'systemd', 'user'];
@@ -310,6 +311,7 @@ export function selectSourcePackages(manifest, role, clients = []) {
310
311
 
311
312
  /** Resolve a packaged compatibility policy into a role-filtered exact selection. */
312
313
  export async function resolveSourcePolicy(manifest, role, clients = [], resolveNpm) {
314
+ const release = releaseBinding(manifest);
313
315
  const names = role === 'server' ? SERVER_PACKAGES : clients.map(name => `@ours.network/${name}`);
314
316
  const packages = {};
315
317
  const sourceNames = new Set();
@@ -332,7 +334,7 @@ export async function resolveSourcePolicy(manifest, role, clients = [], resolveN
332
334
  } else throw new Error(`Missing source policy for ${name}`);
333
335
  }
334
336
  const sources = Object.fromEntries([...sourceNames].map(name => [name, manifest.sources[name]]));
335
- const exact = { ...(sourceNames.size ? { sources } : {}), packages };
337
+ const exact = { ...(sourceNames.size ? { sources } : {}), packages, ...(release ? { release } : {}) };
336
338
  selectSourcePackages(exact, role, clients);
337
339
  return exact;
338
340
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/install",
3
- "version": "1.2.0-nightly.2",
3
+ "version": "1.2.1-nightly.1",
4
4
  "private": false,
5
5
  "description": "The all-in-one ours.network installer: one shared daemon, MCP, cowork, Telegram, Fleet initialization, harness plugins, Human identity, progress UI, and guided next steps.",
6
6
  "type": "module",
@@ -19,17 +19,18 @@
19
19
  ],
20
20
  "license": "FSL-1.1-Apache-2.0",
21
21
  "author": "Adapt Toolkit",
22
- "homepage": "https://github.com/adapt-toolkit/ours-mcp/tree/main/packages/installer#readme",
22
+ "homepage": "https://github.com/adapt-toolkit/ours-network/tree/main/packages/installer#readme",
23
23
  "repository": {
24
24
  "type": "git",
25
- "url": "git+https://github.com/adapt-toolkit/ours-mcp.git",
25
+ "url": "git+https://github.com/adapt-toolkit/ours-network.git",
26
26
  "directory": "packages/installer"
27
27
  },
28
28
  "engines": {
29
29
  "node": ">=22"
30
30
  },
31
31
  "scripts": {
32
- "test": "node --test"
32
+ "test": "node --test",
33
+ "prepack": "node ../../scripts/prepare-installer.mjs"
33
34
  },
34
35
  "dependencies": {
35
36
  "koffi": "3.2.1",