52 lines
4.1 KiB
JavaScript
52 lines
4.1 KiB
JavaScript
// Caller-only descriptions, shared by launch and live bus reads. Inputs are validated policy,
|
|
// never credential references, vars, call arguments or file contents.
|
|
import { decisionAutonomy } from './autonomy.mjs';
|
|
export { decisionAutonomy } from './autonomy.mjs';
|
|
import { GATED_ONLY } from './vocabulary.mjs';
|
|
|
|
const DOMAINS = Object.freeze({ 'git.merge.protected': 'technical', 'policy.change': 'delivery' });
|
|
|
|
export function ownRails({ instance, authority, network, credentials = [], messaging = null, arbiters = {}, launcher = null, autonomy }) {
|
|
const actions = [];
|
|
for (const [kind, held] of Object.entries(authority ?? {})) {
|
|
for (const action of held) {
|
|
const chiefOnly = ['role.hire', 'role.fire'].includes(action) && launcher !== instance;
|
|
const hard = GATED_ONLY.includes(action) || (action === 'role.launch' && launcher !== instance);
|
|
const domains = DOMAINS[action] ? [DOMAINS[action]] : ['delivery', 'technical'];
|
|
const routes = chiefOnly ? [{ to: 'unavailable' }] : hard ? [{ to: 'human' }] : kind === 'withinRole' ? [{ to: 'alone' }]
|
|
: domains.map(domain => ({ domain, to: arbiters[domain] === instance ? 'human' : arbiters[domain] ?? 'unconfigured' }));
|
|
actions.push({ action, routes });
|
|
}
|
|
}
|
|
const resolve = [...(authority?.withinRole ?? []), ...(authority?.crossRole ?? [])]
|
|
.filter(action => action.startsWith('decision.resolve.')).map(right => {
|
|
const domain = right.slice('decision.resolve.'.length);
|
|
const classes = domain === 'technical' ? ['cross-role', 'merge'] : domain === 'delivery' ? ['cross-role', 'policy'] : domain === 'authority' ? ['authority'] : [];
|
|
return { right, classes: classes.map(name => ({ name, level: decisionAutonomy(name, autonomy) })) };
|
|
});
|
|
return {
|
|
actions, resolve, network: network ?? 'unknown',
|
|
credentials: credentials.map(({ service, scopes }) => ({ service, ...(scopes === undefined ? {} : { scopes: structuredClone(scopes) }) })),
|
|
messaging: messaging ? { sendTo: [...messaging.sendTo], receiveFrom: [...messaging.receiveFrom] } : null,
|
|
};
|
|
}
|
|
|
|
export function railsText(rails) {
|
|
const lines = ['## Your rails', '', 'These are your resolved rails. Call my_rails for the current rails after a narrowing.'];
|
|
for (const { action, routes } of rails.actions) {
|
|
const route = routes.map(({ domain, to }) => `${domain ? `${domain}${domain === 'delivery' && routes.length > 1 ? ' (default)' : ''}: ` : ''}${to === 'human' ? 'a person' : to === 'alone' ? 'alone' : to === 'unavailable' ? 'unavailable here; only the configured chief can hire or fire' : to === 'unconfigured' ? 'unconfigured; report missing arbiter' : `arbiter instance ${JSON.stringify(to)}`}`).join('; ');
|
|
lines.push(`- ${action}: ${route}.`);
|
|
}
|
|
if (!rails.actions.length) lines.push('- Held actions: none.');
|
|
for (const { right, classes } of rails.resolve) lines.push(`- ${right} levels: ${classes.map(c => `${c.name}: ${c.level}`).join('; ') || 'unconfigured'}.`);
|
|
lines.push('- A resolve right must be held withinRole, and the decision must route to you. PRD approval also follows the request mode and author checks.');
|
|
lines.push('- Unheld actions are gated. Hard waits always go to a person; raise with blocking false when there is no task. A current spec citation can route other gated work to its arbiter.');
|
|
lines.push('- Revoking a protected instance is a hard wait; ending it follows the chief/person protection checks. Messaging also checks the recipient\'s policy.');
|
|
lines.push(`- Network: ${rails.network}.`);
|
|
for (const credential of rails.credentials) lines.push(`- Credential service ${credential.service}; scopes: ${credential.scopes === undefined ? 'none declared' : JSON.stringify(credential.scopes)}.`);
|
|
if (!rails.credentials.length) lines.push('- Credential services: none.');
|
|
lines.push(`- Messaging sendTo: ${rails.messaging ? rails.messaging.sendTo.join(', ') || 'none' : 'unrestricted by your definition'}.`);
|
|
lines.push(`- Messaging receiveFrom: ${rails.messaging ? rails.messaging.receiveFrom.join(', ') || 'none' : 'unrestricted by your definition'}.`);
|
|
return lines.join('\n');
|
|
}
|