The business file accepts a kind "library" project entry, and each consumer that assumed a project root skips it. An organization write whose projects differ from the file's is refused (M9). The bare mirror is created at the projectRepo path with no remote. library.file and library.status are human only and refuse an agent session with human-required. Candidate herb/row215-on197dde3445abona36523713, Filbert APPROVE rev 2924.
87 lines
6.0 KiB
Diff
87 lines
6.0 KiB
Diff
diff --git a/packages/bus/src/broker.mjs b/packages/bus/src/broker.mjs
|
|
index 135a1699a..f0eb4ae65 100644
|
|
--- a/packages/bus/src/broker.mjs
|
|
+++ b/packages/bus/src/broker.mjs
|
|
@@ -88,6 +88,7 @@ export const TASK_PAGE_MAX = 200;
|
|
// Row 148 (#1642): the human-only verbs behind the Console's document editor. The save is the one
|
|
// request allowed past the ordinary 64 KiB envelope (server.mjs and client.mjs), up to its content cap.
|
|
export const PROJECT_FILE_VERBS = Object.freeze(['project.files', 'project.file', 'project.file.save']);
|
|
+export const LIBRARY_VERBS = Object.freeze(['library.file', 'library.status']);
|
|
export const PROJECT_SAVE_VERB = 'project.file.save';
|
|
export const PROJECT_SAVE_REQUEST_MAX = 1024 * 1024 + 64 * 1024;
|
|
export const WEB_VERBS =Object.freeze(['web.search', 'web.fetch']);
|
|
@@ -279,6 +280,7 @@ export class Broker {
|
|
#organization;
|
|
#credentialVerbs;
|
|
#project;
|
|
+ #library;
|
|
#usage;
|
|
#limits;
|
|
#harnessOn;
|
|
@@ -294,7 +296,7 @@ export class Broker {
|
|
#stops = new Map();
|
|
#clock = 0;
|
|
// credentialStatus is the runtime's metadata-only Credentials.status(); the broker never gets use().
|
|
- constructor({ store, businesses, secretCheck = () => {}, credentialStatus = () => [], organization = null, project = null, usage = null, limits = null, harnessOn = null, credentialVerbs = null, hostGrant = null, hostKeys = null }) {
|
|
+ constructor({ store, businesses, secretCheck = () => {}, credentialStatus = () => [], organization = null, project = null, library = null, usage = null, limits = null, harnessOn = null, credentialVerbs = null, hostGrant = null, hostKeys = null }) {
|
|
if (!object(businesses) || !Object.keys(businesses).length) fail('invalid-business');
|
|
this.#businesses = structuredClone(businesses);
|
|
this.#store = store;
|
|
@@ -306,6 +308,7 @@ export class Broker {
|
|
this.#hostGrant = hostGrant;
|
|
this.#hostKeys = hostKeys;
|
|
this.#project = project;
|
|
+ this.#library = library;
|
|
this.#usage = usage;
|
|
this.#limits = limits;
|
|
// Row 65: harnessOn(business, name) is true when the organization file's plugins.harness switches
|
|
@@ -2192,6 +2195,8 @@ export class Broker {
|
|
return this.#requestProject(s, { verb: request.verb, args: structuredClone(args) });
|
|
if (PROJECT_FILE_VERBS.includes(request.verb))
|
|
return this.#requestProjectFile(s, { verb: request.verb, args: structuredClone(args) });
|
|
+ if (LIBRARY_VERBS.includes(request.verb))
|
|
+ return this.#requestLibrary(s, { verb: request.verb, args: structuredClone(args) });
|
|
if (CREDENTIAL_VERBS.includes(request.verb)) return this.#requestCredential(s, request.verb, structuredClone(args));
|
|
if (["limits", "limits.set", "organization.set", "organization.revision", "plugins", "plugins.set", "instances", "instances.set", "routes", "routes.set"].includes(request.verb)) {
|
|
// Row 123 (#1617, REQ-COS-1, REQ-LIMIT-1): organization.set is limits.set for a role whose
|
|
@@ -2441,6 +2446,26 @@ export class Broker {
|
|
throw this.#refused(s, ProjectFailure && e instanceof ProjectFailure ? new BusError(e.code) : e, request);
|
|
}
|
|
}
|
|
+ // C2-04 M4: only a verified human reads library text or status. Host Git
|
|
+ // functions stay injected, preserving this module's sandbox import closure.
|
|
+ async #requestLibrary(s, request) {
|
|
+ const { verb, args } = request;
|
|
+ try {
|
|
+ this.#human(s);
|
|
+ const fields = verb === 'library.file' ? ['project', 'path'] : ['project'];
|
|
+ keys(args, fields, fields);
|
|
+ if (!this.#library?.readFile || !this.#library?.status) fail('project-not-configured');
|
|
+ const context = { business: this.#business(s.business), project: args.project };
|
|
+ const result = verb === 'library.file'
|
|
+ ? await this.#library.readFile({ ...context, path: args.path })
|
|
+ : await this.#library.status(context);
|
|
+ this.#secretCheck(result);
|
|
+ return result;
|
|
+ } catch (error) {
|
|
+ const Failure = this.#library?.LibraryError;
|
|
+ throw this.#refused(s, Failure && error instanceof Failure ? new BusError(error.code) : error, request);
|
|
+ }
|
|
+ }
|
|
// The eight task verbs reach the trusted S3 handler here. It runs outside any transaction, gets the
|
|
// cap to authorize and record through authorize() and recordTask(); refusals are recorded as in request().
|
|
async requestTask(cap, request, handler) {
|
|
diff --git a/packages/cli/tests/stack-release.test.mjs b/packages/cli/tests/stack-release.test.mjs
|
|
index 7398247ce..0e316fd74 100644
|
|
--- a/packages/cli/tests/stack-release.test.mjs
|
|
+++ b/packages/cli/tests/stack-release.test.mjs
|
|
@@ -1779,7 +1779,7 @@ export class Store {
|
|
const RCOPIES=join(base,'rcopies');mkdirSync(RCOPIES);
|
|
for(const [id,v,froms] of [[B,'3g',['3f']],[C,'3h',['3f','3g']],[A,'3g',['3f']],[C1,'3g',['3f']]]){
|
|
const d=join(RCOPIES,id);
|
|
- for(const p of ['scripts/mosaic','scripts/mosaic-config.mjs','packages/cli/src/config.mjs','packages/cli/src/errors.mjs','packages/bus/src/business.mjs','packages/plugins/src/kinds.mjs','packages/business/src','roles'])cpSync(join(repo,p),join(d,p),{recursive:true});
|
|
+ for(const p of ['scripts/mosaic','scripts/mosaic-config.mjs','packages/cli/src/config.mjs','packages/cli/src/errors.mjs','packages/bus/src/business.mjs','packages/plugins/src/kinds.mjs','packages/business/src','packages/library/src','roles'])cpSync(join(repo,p),join(d,p),{recursive:true});
|
|
if(id===C1)writeFileSync(join(d,'scripts','mosaic'),`#!/usr/bin/env bash\nexec node "${join(RCOPIES,B,'packages','cli','src','cli.mjs')}" "$@"\n`,{mode:0o755});
|
|
for(const [f,text] of [['packages/cli/src/cli.mjs',id===A?'// fixture host from before row 163\nprocess.exit(1);\n':script(rHost)],['packages/webui/src/cli.mjs',script(rConsole)],['packages/bus/src/store.mjs',rStore(v,froms)],['packages/bus/src/migrate-schema.mjs',script(rMigrate)],['packages/bus/src/broker.mjs','export class BusError extends Error { constructor(code) { super(code); this.code = code; } }\n']]){mkdirSync(dirname(join(d,f)),{recursive:true});writeFileSync(join(d,f),text)}
|
|
execFileSync('chmod',['-R','a-w',d]);
|