@awak-app/simy-cli 0.5.6 → 0.5.7

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 CHANGED
@@ -380,3 +380,14 @@ Publishing is handled by GitHub Actions in `.github/workflows/publish.yml`.
380
380
  `@awak-app/simy-cli`.
381
381
  - The package check inspects the final npm tarball contents and fails if any
382
382
  source map file would be published.
383
+
384
+ ### Repository system scopes
385
+
386
+ Version 0.5.7 supports signed `system_scope` snapshots from Quality Monitor.
387
+ Each check pins the repository's connected component and graph revision. A new
388
+ check revalidates scoped bundles online; a stale or unavailable scope cannot fall
389
+ back to an older cached component. Minimum and full check proofs retain the
390
+ original snapshot. A scope revision change requires a fresh minimum check.
391
+ Deploy this CLI and the web capability proxy before enabling the backend scope
392
+ requirement. Older clients receive an explicit upgrade error, never an expanded
393
+ organization-wide bundle.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awak-app/simy-cli",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "description": "Local SIMY executor for non-coding Local Tasks and Agentic Loop runs.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,7 +13,7 @@ import {
13
13
  } from "./durable-local-task-steps-contract.js";
14
14
  import { buildExecutionCapabilityContract } from "./execution-capability-contract.js";
15
15
 
16
- export const CLI_VERSION = "0.5.6";
16
+ export const CLI_VERSION = "0.5.7";
17
17
  export const CLI_API_CONTRACT_VERSION = 10;
18
18
 
19
19
  export function withCliContract(
@@ -41,13 +41,13 @@ export async function loadKnowledgeBundle({
41
41
  }
42
42
  const cacheIdentity = normalizedCacheIdentity({ endpoint, accountId, organizationId, repository });
43
43
  const location = cacheLocation(cacheRoot, cacheIdentity);
44
- const validation = { cliVersion, publicKeys, organizationId };
44
+ const validation = { cliVersion, publicKeys, organizationId, repository };
45
45
  const cached = await readVerifiedCache(location, validation, cacheIdentity);
46
46
  const fresh =
47
47
  cached &&
48
48
  now - cached.fetchedAt < ttlMs &&
49
49
  Date.parse(cached.bundle.expires_at) > now;
50
- if (!offline && endpoint && (!fresh || refresh)) {
50
+ if (!offline && endpoint && (!fresh || refresh || cached?.bundle.system_scope)) {
51
51
  try {
52
52
  const fetched = await fetchBundle({ endpoint, repository, cliVersion, token, organizationId, etag: cached?.etag, fetchImpl });
53
53
  if (fetched.notModified) {
@@ -56,6 +56,7 @@ export async function loadKnowledgeBundle({
56
56
  await writeMetadata(location, { ...cached.metadata, fetched_at: new Date(now).toISOString(), etag: fetched.etag || cached.etag });
57
57
  return result(cached.bundle, [], "cache_revalidated");
58
58
  }
59
+ if (cached?.bundle.system_scope && !fetched.bundle?.system_scope) throw nonFallbackError("SQM Cloud attempted to remove the pinned system scope contract.");
59
60
  const discoveredTrust = signingTrustForFetch({
60
61
  bundle: fetched.bundle,
61
62
  endpoint,
@@ -81,11 +82,12 @@ export async function loadKnowledgeBundle({
81
82
  return result(bundle, [], "cloud");
82
83
  } catch (error) {
83
84
  if (error?.code === "incompatible_cli") throw error;
84
- if (error?.fallbackAllowed !== false && cached && Date.parse(cached.bundle.expires_at) > now) return result(cached.bundle, [`SQM Cloud unavailable; using last verified bundle: ${message(error)}`], "stale_cache");
85
+ if (error?.fallbackAllowed !== false && cached && !cached.bundle.system_scope && Date.parse(cached.bundle.expires_at) > now) return result(cached.bundle, [`SQM Cloud unavailable; using last verified bundle: ${message(error)}`], "stale_cache");
85
86
  return result(null, [`SQM knowledge could not be used; shadow checks were skipped: ${message(error)}`], "unavailable");
86
87
  }
87
88
  }
88
89
  if (cached) {
90
+ if (cached.bundle.system_scope) return result(null, ["SQM system scope must be revalidated online before a new check."], "unavailable");
89
91
  if (Date.parse(cached.bundle.expires_at) <= now) return result(null, ["The cached SQM knowledge bundle is expired; shadow checks were skipped."], "unavailable");
90
92
  const warnings = [];
91
93
  if (offline) warnings.push("SQM is offline; using the last verified knowledge bundle.");
@@ -96,10 +98,12 @@ export async function loadKnowledgeBundle({
96
98
  return result(null, [`SQM has no verified cache (${reason}); shadow checks were skipped.`], "unavailable");
97
99
  }
98
100
 
99
- export function verifyAndValidate(bundle, { cliVersion, publicKeys, organizationId } = {}) {
101
+ export function verifyAndValidate(bundle, { cliVersion, publicKeys, organizationId, repository } = {}) {
100
102
  try { validateKnowledgeBundle(bundle, { cliVersion }); } catch (error) { throw nonFallbackError(message(error), error); }
101
103
  if (organizationId && bundle.organization_id !== organizationId) throw nonFallbackError(`SQM bundle organization mismatch: expected ${organizationId}.`);
104
+ if (bundle.system_scope && repository && bundle.system_scope.repository !== repository.toLowerCase()) throw nonFallbackError("SQM bundle repository system scope does not match this repository.");
102
105
  for (const module of bundle.modules) {
106
+ if (bundle.system_scope && module.scope.visibility === "organization" && !module.scope.repositories.some((repo) => bundle.system_scope.repositories.includes(repo.toLowerCase()))) throw nonFallbackError(`SQM module ${module.id} is outside the repository system.`);
103
107
  if (module.scope.visibility === "organization" && module.scope.organization_id !== bundle.organization_id) throw nonFallbackError(`SQM module ${module.id} is scoped to a different organization.`);
104
108
  }
105
109
  const keyId = bundle.integrity.key_id;
@@ -166,6 +170,7 @@ async function fetchBundle({ endpoint, repository, cliVersion, token, organizati
166
170
  const url = new URL(endpoint);
167
171
  url.searchParams.set("repository", repository);
168
172
  url.searchParams.set("cli_version", cliVersion);
173
+ url.searchParams.set("scope_version", "1");
169
174
  const headers = { Accept: "application/json" };
170
175
  if (token) headers.Authorization = `Bearer ${token}`;
171
176
  if (organizationId) headers["x-simy-org-id"] = organizationId;
package/src/sqm/index.js CHANGED
@@ -132,7 +132,7 @@ function toLegacyCheck(minCheck, loaded) {
132
132
  }
133
133
 
134
134
  function bundleIdentity(bundle) {
135
- return { id: bundle.bundle_id, version: bundle.version, digest: bundle.integrity.digest, signing_key_id: bundle.integrity.key_id };
135
+ return { id: bundle.bundle_id, version: bundle.version, digest: bundle.integrity.digest, signing_key_id: bundle.integrity.key_id, ...(bundle.system_scope ? { system_scope: bundle.system_scope } : {}) };
136
136
  }
137
137
 
138
138
  function unavailable(warning, repository = null) {
package/src/sqm/proof.js CHANGED
@@ -57,6 +57,7 @@ export function buildProof({ organizationId, repository, bundle, minCheck, evalu
57
57
  version: bundle.version,
58
58
  digest: bundle.integrity.digest,
59
59
  signing_key_id: bundle.integrity.key_id,
60
+ ...(bundle.system_scope ? { system_scope: bundle.system_scope } : {}),
60
61
  },
61
62
  min_check: {
62
63
  result_digest: minCheck.result_digest,
@@ -17,13 +17,27 @@ const SCENARIO_REQUIREMENTS = new Set(["local", "requires_human", "requires_post
17
17
  export function validateKnowledgeBundle(bundle, { cliVersion }) {
18
18
  const errors = [];
19
19
  object(bundle, "$", errors);
20
- exactKeys(bundle, ["schema_version", "bundle_id", "version", "organization_id", "generated_at", "expires_at", "modules", "integrity"], "$", errors);
20
+ exactKeys(bundle, ["schema_version", "bundle_id", "version", "organization_id", "generated_at", "expires_at", "modules", "integrity", "system_scope"], "$", errors);
21
21
  equal(bundle?.schema_version, "1.0.0", "$.schema_version", errors);
22
22
  stringPattern(bundle?.bundle_id, /^[a-z][a-z0-9._-]{2,127}$/, "$.bundle_id", errors);
23
23
  stringPattern(bundle?.version, /^[0-9]{4}\.[0-9]{2}\.[0-9]{2}\.[0-9]+$/, "$.version", errors);
24
24
  nullableString(bundle?.organization_id, 200, "$.organization_id", errors);
25
25
  dateTime(bundle?.generated_at, "$.generated_at", errors);
26
26
  dateTime(bundle?.expires_at, "$.expires_at", errors);
27
+ if (bundle?.system_scope !== undefined) {
28
+ const scope = bundle.system_scope;
29
+ object(scope, "$.system_scope", errors);
30
+ exactKeys(scope, ["organization_id", "repository", "graph_revision", "repositories"], "$.system_scope", errors);
31
+ equal(scope?.organization_id, bundle.organization_id, "$.system_scope.organization_id", errors);
32
+ stringPattern(scope?.repository, REPOSITORY, "$.system_scope.repository", errors);
33
+ if (!Number.isSafeInteger(scope?.graph_revision) || scope.graph_revision < 0) errors.push("$.system_scope.graph_revision must be a nonnegative integer");
34
+ if (!Array.isArray(scope?.repositories) || !scope.repositories.length || !scope.repositories.includes(scope.repository)) {
35
+ errors.push("$.system_scope.repositories must contain the target repository");
36
+ } else {
37
+ for (const repo of scope.repositories) stringPattern(repo, REPOSITORY, "$.system_scope.repositories[]", errors);
38
+ if (new Set(scope.repositories).size !== scope.repositories.length) errors.push("$.system_scope.repositories must be unique");
39
+ }
40
+ }
27
41
  if (!Array.isArray(bundle?.modules)) errors.push("$.modules must be an array");
28
42
  for (const [index, module] of (bundle?.modules || []).entries()) {
29
43
  validateModule(module, `$.modules[${index}]`, errors, cliVersion);