@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 +11 -0
- package/package.json +1 -1
- package/src/cli-contract.js +1 -1
- package/src/sqm/bundle-store.js +9 -4
- package/src/sqm/index.js +1 -1
- package/src/sqm/proof.js +1 -0
- package/src/sqm/validation.js +15 -1
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
package/src/cli-contract.js
CHANGED
|
@@ -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.
|
|
16
|
+
export const CLI_VERSION = "0.5.7";
|
|
17
17
|
export const CLI_API_CONTRACT_VERSION = 10;
|
|
18
18
|
|
|
19
19
|
export function withCliContract(
|
package/src/sqm/bundle-store.js
CHANGED
|
@@ -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,
|
package/src/sqm/validation.js
CHANGED
|
@@ -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);
|