@etiquekit/etq 1.0.11 → 1.0.13
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/AGENTS.md +19 -26
- package/LICENSE +2 -3
- package/NOTICE +6 -4
- package/QuickStart.md +35 -47
- package/README.md +54 -56
- package/bin/etiquette +1 -5
- package/bin/etiquette-core +1 -5
- package/docs/ARCHITECTURE.md +5 -5
- package/docs/CODEX_CLIENT_COMPATIBILITY.md +1 -1
- package/docs/CONCEPTS.md +7 -23
- package/docs/CONCEPT_STATUS.md +66 -0
- package/docs/CORE_PROFILE.md +3 -5
- package/docs/LANE_PROVISIONING.md +7 -2
- package/docs/README.md +4 -4
- package/docs/RELEASE_SURFACE_AUDIT.md +7 -6
- package/docs/SEAT_DISCIPLINE.md +55 -91
- package/docs/SEAT_PROVISIONING.md +19 -22
- package/docs/TEAM_HANDOFF.md +21 -21
- package/docs/WORKTREE_QOL.md +7 -6
- package/docs/contracts/ledger-entry/README.md +11 -11
- package/docs/contracts/ledger-entry/ledger-entry.v0.2.md +3 -3
- package/docs/contracts/ledger-entry/ledger-entry.v0.md +8 -8
- package/package.json +11 -11
- package/packages/control/src/authority/lease.ts +261 -0
- package/packages/control/src/authority/node-delegation.ts +257 -0
- package/packages/control/src/authority/rig-conductor.ts +632 -0
- package/packages/control/src/cli/argv.ts +155 -0
- package/packages/control/src/cli/commands/console.ts +200 -0
- package/packages/control/src/cli/commands/dispatch-core.ts +89 -0
- package/packages/control/src/cli/commands/dispatch.ts +279 -0
- package/packages/control/src/cli/commands/harness.ts +89 -0
- package/packages/control/src/cli/commands/hook.ts +50 -0
- package/packages/control/src/cli/commands/ledger.ts +91 -0
- package/packages/control/src/cli/commands/local-workflow.ts +4686 -0
- package/packages/control/src/cli/commands/memory.ts +445 -0
- package/packages/control/src/cli/commands/release.ts +108 -0
- package/packages/control/src/cli/commands/rubric.ts +103 -0
- package/packages/control/src/cli/commands/seat.ts +179 -0
- package/packages/control/src/cli/commands/session.ts +127 -0
- package/packages/control/src/cli/commands/supervision.ts +246 -0
- package/packages/control/src/cli/commands/sync.ts +119 -0
- package/packages/control/src/cli/commands/workflow.ts +86 -0
- package/packages/control/src/cli/commands/workspace.ts +50 -0
- package/packages/control/src/cli/core-usage.ts +34 -0
- package/packages/control/src/cli/prompt.ts +67 -0
- package/packages/control/src/cli/supervision-deps.ts +44 -0
- package/packages/control/src/cli/usage.ts +241 -0
- package/packages/control/src/cli.ts +207 -0
- package/packages/control/src/core-cli.ts +50 -0
- package/packages/control/src/dispatch/decision.ts +202 -0
- package/packages/control/src/dispatch/projection.ts +293 -0
- package/packages/control/src/dispatch/record.ts +153 -0
- package/packages/control/src/engagement/project.ts +170 -0
- package/packages/control/src/fs.ts +19 -0
- package/packages/control/src/harness/pruning.ts +406 -0
- package/packages/control/src/hooks/dispatcher.ts +117 -0
- package/packages/control/src/hooks/outbox.ts +86 -0
- package/packages/control/src/hooks/sanitize.ts +6 -0
- package/packages/control/src/hooks/types.ts +34 -0
- package/packages/control/src/index.ts +384 -0
- package/packages/control/src/ledger/entry.ts +303 -0
- package/packages/control/src/ledger/indexer.ts +542 -0
- package/packages/control/src/memory/context.ts +149 -0
- package/packages/control/src/memory/drain-import.ts +207 -0
- package/packages/control/src/memory/indexer.ts +284 -0
- package/packages/control/src/memory/query.ts +75 -0
- package/packages/control/src/memory/sanitize.ts +50 -0
- package/packages/control/src/memory/sharded-drain-import.ts +212 -0
- package/packages/control/src/memory/status.ts +211 -0
- package/packages/control/src/memory/store-lifecycle.ts +509 -0
- package/packages/control/src/memory/store.ts +284 -0
- package/packages/control/src/memory/types.ts +146 -0
- package/packages/control/src/parity/surfaces.ts +748 -0
- package/packages/control/src/project.ts +141 -0
- package/packages/control/src/projection/local-ledger-view.ts +373 -0
- package/packages/control/src/projection/return-enforcement.ts +48 -0
- package/packages/control/src/projection/timeline-preview.ts +539 -0
- package/packages/control/src/projection/timeline.ts +708 -0
- package/packages/control/src/release/readiness.ts +831 -0
- package/packages/control/src/rubric/loader.ts +326 -0
- package/packages/control/src/rubric/promotion.ts +54 -0
- package/packages/control/src/rubric/runner.ts +159 -0
- package/packages/control/src/rubric/types.ts +158 -0
- package/packages/control/src/seat/owner-card.ts +388 -0
- package/packages/control/src/seat/readiness.ts +834 -0
- package/packages/control/src/session/runbook.ts +431 -0
- package/packages/control/src/shared/sanitize.ts +49 -0
- package/packages/control/src/supervision/action-classes.ts +192 -0
- package/packages/control/src/supervision/command-apply.ts +378 -0
- package/packages/control/src/supervision/errors.ts +14 -0
- package/packages/control/src/supervision/event-replay.ts +155 -0
- package/packages/control/src/supervision/events.ts +109 -0
- package/packages/control/src/supervision/index.ts +16 -0
- package/packages/control/src/supervision/manifest.ts +127 -0
- package/packages/control/src/supervision/paths.ts +49 -0
- package/packages/control/src/supervision/projection-adapter.ts +274 -0
- package/packages/control/src/supervision/projection.ts +75 -0
- package/packages/control/src/supervision/rebuild.ts +99 -0
- package/packages/control/src/supervision/session-open.ts +131 -0
- package/packages/control/src/supervision/session-read.ts +99 -0
- package/packages/control/src/supervision/sqlite-impl.ts +71 -0
- package/packages/control/src/supervision/sqlite.ts +121 -0
- package/packages/control/src/supervision/store-rows.ts +371 -0
- package/packages/control/src/supervision/turn-close.ts +154 -0
- package/packages/control/src/supervision/turn-open.ts +284 -0
- package/packages/control/src/sync/event-log-merge-driver.ts +263 -0
- package/packages/control/src/sync/join-plan.ts +375 -0
- package/packages/control/src/sync/outbox.ts +492 -0
- package/packages/control/src/workflow/evaluator.ts +140 -0
- package/packages/control/src/workflow/loader.ts +200 -0
- package/packages/control/src/workflow/types.ts +90 -0
- package/packages/control/src/workspace/authority.ts +499 -0
- package/packages/protocol/src/guards.ts +119 -0
- package/packages/protocol/src/huddle-board.ts +198 -0
- package/packages/protocol/src/huddle.ts +295 -0
- package/packages/protocol/src/incident.ts +251 -0
- package/packages/protocol/src/index.ts +8 -0
- package/packages/protocol/src/interfaces.ts +107 -0
- package/packages/protocol/src/packet-profile.ts +195 -0
- package/packages/protocol/src/state.ts +81 -0
- package/packages/protocol/src/types.ts +434 -0
- package/release/lineage.v0.json +14 -0
- package/scripts/release-candidate-verify.sh +175 -0
- package/scripts/release-checksum.sh +25 -0
- package/scripts/release-pack-canary.sh +97 -0
- package/scripts/release-scan.sh +249 -0
- package/scripts/release-sign.sh +34 -0
- package/templates/etiquette-vanilla-v0/README.md +2 -3
- package/templates/etiquette-vanilla-v0/source/control-seat/README.md +0 -6
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-doctor +0 -5
- package/templates/etiquette-vanilla-v0/validate-vanilla.sh +0 -5
- package/templates/seat-packs-v0/README.md +3 -2
- package/templates/seat-packs-v0/source/claude-code-seat/README.md +3 -29
- package/templates/seat-packs-v0/source/codex-seat/README.md +3 -17
- package/templates/seat-packs-v0/source/gemini-seat/README.md +3 -23
- package/templates/seat-packs-v0/source/ollama-seat/README.md +3 -3
- package/templates/seat-packs-v0/source/openrouter-seat/README.md +3 -3
- package/docs/AGENT_SPAWNING.md +0 -78
- package/docs/INTEGRATION_UI.md +0 -215
- package/docs/OPERATOR_PLAYBOOK.md +0 -77
- package/docs/TAG_ROUTE.md +0 -95
- package/lib/etiquette-core.js +0 -315
- package/lib/etiquette.js +0 -1160
- package/templates/DISPATCH_PREMISES.md +0 -39
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/access-assurance-check +0 -55
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/ACCESS-ASSURANCE.md +0 -39
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/workspace-secure-profile.v0.json +0 -53
- package/templates/hosted-receiver/README.md +0 -41
- package/templates/hosted-receiver/w1-github-webhook-receiver.mjs +0 -129
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ACTION_CLASSES,
|
|
3
|
+
PROJECTION_POLICIES,
|
|
4
|
+
SUPERVISION_COMMANDS,
|
|
5
|
+
TURN_RESULTS,
|
|
6
|
+
ALLOWED_COMMAND_IDS,
|
|
7
|
+
type ActionClass,
|
|
8
|
+
type AllowedCommandId,
|
|
9
|
+
type ProjectionPolicy,
|
|
10
|
+
type SupervisionCommand,
|
|
11
|
+
type TurnResult,
|
|
12
|
+
type Surface,
|
|
13
|
+
} from '../../../protocol/src/index';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Argv parsing + per-flag coercion helpers. Extracted from the former
|
|
17
|
+
* monolithic `cli.ts`. No I/O, no side effects — pure string-shape checks.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export interface ParsedArgv {
|
|
21
|
+
positionals: string[];
|
|
22
|
+
flags: Record<string, string | boolean>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function parseArgs(argv: string[]): ParsedArgv {
|
|
26
|
+
const positionals: string[] = [];
|
|
27
|
+
const flags: Record<string, string | boolean> = {};
|
|
28
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
29
|
+
const token = argv[i];
|
|
30
|
+
if (!token.startsWith('--')) {
|
|
31
|
+
positionals.push(token);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const key = token.slice(2);
|
|
35
|
+
const next = argv[i + 1];
|
|
36
|
+
if (!next || next.startsWith('--')) {
|
|
37
|
+
flags[key] = true;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
flags[key] = next;
|
|
41
|
+
i += 1;
|
|
42
|
+
}
|
|
43
|
+
return { positionals, flags };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function parseSurfaces(
|
|
47
|
+
raw: string | boolean | undefined,
|
|
48
|
+
availableSurfaces: string[],
|
|
49
|
+
): Surface[] {
|
|
50
|
+
if (!raw || raw === true) {
|
|
51
|
+
return [...availableSurfaces];
|
|
52
|
+
}
|
|
53
|
+
const values = String(raw).split(',').map((value) => value.trim()).filter(Boolean);
|
|
54
|
+
return values.map((value) => {
|
|
55
|
+
if (!availableSurfaces.includes(value)) {
|
|
56
|
+
throw new Error(`unknown surface: ${value}`);
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function parseList(raw: string | boolean | undefined): string[] {
|
|
63
|
+
if (!raw || raw === true) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
return String(raw).split(',').map((value) => value.trim()).filter(Boolean);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function requireString(
|
|
70
|
+
flags: Record<string, string | boolean>,
|
|
71
|
+
key: string,
|
|
72
|
+
): string {
|
|
73
|
+
const v = flags[key];
|
|
74
|
+
if (typeof v !== 'string' || v.length === 0) {
|
|
75
|
+
throw new Error(`--${key} is required`);
|
|
76
|
+
}
|
|
77
|
+
return v;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function optionalString(
|
|
81
|
+
flags: Record<string, string | boolean>,
|
|
82
|
+
key: string,
|
|
83
|
+
): string | undefined {
|
|
84
|
+
const v = flags[key];
|
|
85
|
+
return typeof v === 'string' && v.length > 0 ? v : undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function parseInt32(raw: string | boolean | undefined, key: string): number {
|
|
89
|
+
if (typeof raw !== 'string') {
|
|
90
|
+
throw new Error(`--${key} is required and must be an integer`);
|
|
91
|
+
}
|
|
92
|
+
const n = Number.parseInt(raw, 10);
|
|
93
|
+
if (!Number.isFinite(n)) {
|
|
94
|
+
throw new Error(`--${key} must be an integer (got ${raw})`);
|
|
95
|
+
}
|
|
96
|
+
return n;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function parseAllowedCommands(
|
|
100
|
+
raw: string | boolean | undefined,
|
|
101
|
+
key: string,
|
|
102
|
+
): AllowedCommandId[] {
|
|
103
|
+
const values = parseList(raw);
|
|
104
|
+
const bad = values.filter((v) => !(ALLOWED_COMMAND_IDS as readonly string[]).includes(v));
|
|
105
|
+
if (bad.length > 0) {
|
|
106
|
+
throw new Error(`--${key} contains unknown operation IDs: ${bad.join(', ')}`);
|
|
107
|
+
}
|
|
108
|
+
return values as AllowedCommandId[];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function parseActionClass(
|
|
112
|
+
raw: string | boolean | undefined,
|
|
113
|
+
key: string,
|
|
114
|
+
): ActionClass {
|
|
115
|
+
if (typeof raw !== 'string' || !(ACTION_CLASSES as readonly string[]).includes(raw)) {
|
|
116
|
+
throw new Error(`--${key} must be one of: ${ACTION_CLASSES.join(', ')}`);
|
|
117
|
+
}
|
|
118
|
+
return raw as ActionClass;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function parseProjectionPolicy(
|
|
122
|
+
raw: string | boolean | undefined,
|
|
123
|
+
): ProjectionPolicy {
|
|
124
|
+
if (raw === undefined || raw === true) return 'on_state_change';
|
|
125
|
+
if (!(PROJECTION_POLICIES as readonly string[]).includes(raw as string)) {
|
|
126
|
+
throw new Error(`--projection-policy must be one of: ${PROJECTION_POLICIES.join(', ')}`);
|
|
127
|
+
}
|
|
128
|
+
return raw as ProjectionPolicy;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function parseTurnResult(raw: string | boolean | undefined): TurnResult {
|
|
132
|
+
if (typeof raw !== 'string' || !(TURN_RESULTS as readonly string[]).includes(raw)) {
|
|
133
|
+
throw new Error(`--result must be one of: ${TURN_RESULTS.join(', ')}`);
|
|
134
|
+
}
|
|
135
|
+
return raw as TurnResult;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function parseSupervisionCommandName(raw: string | undefined): SupervisionCommand {
|
|
139
|
+
if (!raw || !(SUPERVISION_COMMANDS as readonly string[]).includes(raw)) {
|
|
140
|
+
throw new Error(`<command> must be one of: ${SUPERVISION_COMMANDS.join(', ')}`);
|
|
141
|
+
}
|
|
142
|
+
return raw as SupervisionCommand;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function requirePositional(
|
|
146
|
+
positionals: string[],
|
|
147
|
+
index: number,
|
|
148
|
+
name: string,
|
|
149
|
+
): string {
|
|
150
|
+
const value = positionals[index];
|
|
151
|
+
if (!value) {
|
|
152
|
+
throw new Error(`<${name}> positional argument is required`);
|
|
153
|
+
}
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { optionalString, parseInt32 } from '../argv';
|
|
3
|
+
import { renderLocalBoard } from './local-workflow';
|
|
4
|
+
import { buildConsoleReadModel, type ConsoleReadModel } from '../../parity/surfaces';
|
|
5
|
+
|
|
6
|
+
const CONSOLE_VERBS = new Set(['snapshot', 'serve', 'board']);
|
|
7
|
+
|
|
8
|
+
export function isConsoleCommand(command: string): boolean {
|
|
9
|
+
return command === 'console';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function parsePort(raw: string | boolean | undefined): number {
|
|
13
|
+
if (raw === undefined) return 4173;
|
|
14
|
+
const port = parseInt32(raw, 'port');
|
|
15
|
+
if (port < 1 || port > 65535) throw new Error('--port must be between 1 and 65535');
|
|
16
|
+
return port;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function renderSnapshot(model: ConsoleReadModel): string {
|
|
20
|
+
return [
|
|
21
|
+
'console_read_model:',
|
|
22
|
+
` generated_at: ${model.generated_at}`,
|
|
23
|
+
` ledger_entries: ${model.counts.ledger_entries}`,
|
|
24
|
+
` receipts: ${model.counts.receipts}`,
|
|
25
|
+
` tasks: ${model.counts.tasks}`,
|
|
26
|
+
` outbox_items: ${model.counts.outbox_items}`,
|
|
27
|
+
` provisioning_events: ${model.counts.provisioning_events}`,
|
|
28
|
+
` owner_cards: ${model.counts.owner_cards}`,
|
|
29
|
+
` harness_inventory_items: ${model.harness_maintenance.counts.inventory_items}`,
|
|
30
|
+
` harness_pruning_receipts: ${model.harness_maintenance.counts.pruning_receipts}`,
|
|
31
|
+
` harness_validation_errors: ${model.harness_maintenance.counts.validation_errors}`,
|
|
32
|
+
` read_only: ${String(model.authority_boundary.read_only)}`,
|
|
33
|
+
].join('\n');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function escapeHtml(value: string): string {
|
|
37
|
+
return value
|
|
38
|
+
.replace(/&/g, '&')
|
|
39
|
+
.replace(/</g, '<')
|
|
40
|
+
.replace(/>/g, '>')
|
|
41
|
+
.replace(/"/g, '"');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function html(model: ConsoleReadModel): string {
|
|
45
|
+
const seat = model.seat_readiness?.seat ? String(model.seat_readiness.seat) : 'none';
|
|
46
|
+
const ledger = model.recent_ledger.length > 0
|
|
47
|
+
? model.recent_ledger.map((line) => `<li>${escapeHtml(line.replace(/^- /, ''))}</li>`).join('')
|
|
48
|
+
: '<li>No ledger entries found.</li>';
|
|
49
|
+
const provisioning = model.provisioning_events.length > 0
|
|
50
|
+
? model.provisioning_events.map((event) => (
|
|
51
|
+
`<li><strong>${escapeHtml(event.target.cluster_name)}</strong> ${escapeHtml(event.event_kind)}`
|
|
52
|
+
+ ` — <code>${escapeHtml(event.plan_ref)}</code></li>`
|
|
53
|
+
)).join('')
|
|
54
|
+
: '<li>No provisioning events found.</li>';
|
|
55
|
+
const ownerCards = model.owner_cards.cards.length > 0
|
|
56
|
+
? model.owner_cards.cards.map((card) => (
|
|
57
|
+
`<li><strong>${escapeHtml(card.seat_id)}</strong> owned by <code>${escapeHtml(card.accountability.owner_principal)}</code>`
|
|
58
|
+
+ ` — ${escapeHtml(card.job)}`
|
|
59
|
+
+ ` — review stale: ${escapeHtml(String(card.review.is_stale))}`
|
|
60
|
+
+ ` — warnings: ${escapeHtml(card.warnings.join(', ') || 'none')}</li>`
|
|
61
|
+
)).join('')
|
|
62
|
+
: '<li>No owner cards found.</li>';
|
|
63
|
+
const ownerCardErrors = model.owner_cards.validation_errors.length > 0
|
|
64
|
+
? model.owner_cards.validation_errors.map((entry) => (
|
|
65
|
+
`<li><code>${escapeHtml(entry.ref)}</code> — ${escapeHtml(entry.reason)}</li>`
|
|
66
|
+
)).join('')
|
|
67
|
+
: '<li>No owner card validation errors found.</li>';
|
|
68
|
+
const harnessReceipts = model.harness_maintenance.pruning_receipts.length > 0
|
|
69
|
+
? model.harness_maintenance.pruning_receipts.map((receipt) => (
|
|
70
|
+
`<li><strong>${escapeHtml(receipt.object_id)}</strong> ${escapeHtml(receipt.proposed_disposition)}`
|
|
71
|
+
+ ` — canary ${escapeHtml(receipt.canary_verdict)}`
|
|
72
|
+
+ ` — <code>${escapeHtml(receipt.receipt_ref)}</code></li>`
|
|
73
|
+
)).join('')
|
|
74
|
+
: '<li>No harness pruning receipts found.</li>';
|
|
75
|
+
const harnessErrors = model.harness_maintenance.validation_errors.length > 0
|
|
76
|
+
? model.harness_maintenance.validation_errors.map((entry) => (
|
|
77
|
+
`<li><code>${escapeHtml(entry.ref)}</code> — ${escapeHtml(entry.reason)}</li>`
|
|
78
|
+
)).join('')
|
|
79
|
+
: '<li>No harness validation errors found.</li>';
|
|
80
|
+
return `<!doctype html>
|
|
81
|
+
<html lang="en">
|
|
82
|
+
<head>
|
|
83
|
+
<meta charset="utf-8">
|
|
84
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
85
|
+
<title>Etiquette Console</title>
|
|
86
|
+
<style>
|
|
87
|
+
body { margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: #1f2937; background: #f8fafc; }
|
|
88
|
+
main { max-width: 980px; margin: 0 auto; padding: 32px 20px 48px; }
|
|
89
|
+
h1 { font-size: 30px; margin: 0 0 8px; letter-spacing: 0; }
|
|
90
|
+
.subtle { color: #64748b; margin: 0 0 24px; }
|
|
91
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; margin: 18px 0 28px; }
|
|
92
|
+
.metric { border: 1px solid #dbe3ef; border-radius: 8px; padding: 14px; background: #fff; }
|
|
93
|
+
.metric span { display: block; font-size: 13px; color: #64748b; }
|
|
94
|
+
.metric strong { display: block; font-size: 28px; margin-top: 4px; }
|
|
95
|
+
section { margin-top: 28px; }
|
|
96
|
+
code { background: #e2e8f0; padding: 2px 5px; border-radius: 4px; }
|
|
97
|
+
ul { padding-left: 20px; }
|
|
98
|
+
.boundary { border-left: 4px solid #2563eb; padding: 10px 14px; background: #eff6ff; }
|
|
99
|
+
</style>
|
|
100
|
+
</head>
|
|
101
|
+
<body>
|
|
102
|
+
<main>
|
|
103
|
+
<h1>Etiquette Console</h1>
|
|
104
|
+
<p class="subtle">Read-only workspace view generated ${escapeHtml(model.generated_at)}.</p>
|
|
105
|
+
<div class="grid">
|
|
106
|
+
<div class="metric"><span>Ledger entries</span><strong>${model.counts.ledger_entries}</strong></div>
|
|
107
|
+
<div class="metric"><span>Receipts</span><strong>${model.counts.receipts}</strong></div>
|
|
108
|
+
<div class="metric"><span>Tasks</span><strong>${model.counts.tasks}</strong></div>
|
|
109
|
+
<div class="metric"><span>Outbox items</span><strong>${model.counts.outbox_items}</strong></div>
|
|
110
|
+
<div class="metric"><span>Provisioning events</span><strong>${model.counts.provisioning_events}</strong></div>
|
|
111
|
+
<div class="metric"><span>Owner cards</span><strong>${model.counts.owner_cards}</strong></div>
|
|
112
|
+
<div class="metric"><span>Harness inventory</span><strong>${model.harness_maintenance.counts.inventory_items}</strong></div>
|
|
113
|
+
<div class="metric"><span>Pruning receipts</span><strong>${model.harness_maintenance.counts.pruning_receipts}</strong></div>
|
|
114
|
+
<div class="metric"><span>Harness errors</span><strong>${model.harness_maintenance.counts.validation_errors}</strong></div>
|
|
115
|
+
</div>
|
|
116
|
+
<section class="boundary">
|
|
117
|
+
<strong>Authority boundary:</strong> this console cannot emit events, attach grants, merge, or close work.
|
|
118
|
+
</section>
|
|
119
|
+
<section>
|
|
120
|
+
<h2>Seat readiness</h2>
|
|
121
|
+
<p>Current seat: <code>${escapeHtml(seat)}</code></p>
|
|
122
|
+
</section>
|
|
123
|
+
<section>
|
|
124
|
+
<h2>Seat ownership</h2>
|
|
125
|
+
<ul>${ownerCards}</ul>
|
|
126
|
+
<h3>Validation</h3>
|
|
127
|
+
<ul>${ownerCardErrors}</ul>
|
|
128
|
+
</section>
|
|
129
|
+
<section>
|
|
130
|
+
<h2>Recent ledger</h2>
|
|
131
|
+
<ul>${ledger}</ul>
|
|
132
|
+
</section>
|
|
133
|
+
<section>
|
|
134
|
+
<h2>Provisioning</h2>
|
|
135
|
+
<ul>${provisioning}</ul>
|
|
136
|
+
</section>
|
|
137
|
+
<section>
|
|
138
|
+
<h2>Harness maintenance</h2>
|
|
139
|
+
<p>Keep ${model.harness_maintenance.counts.keep}; consolidate ${model.harness_maintenance.counts.consolidate}; quarantine ${model.harness_maintenance.counts.quarantine}; blocked ${model.harness_maintenance.counts.blocked}; delete-ready ${model.harness_maintenance.counts['delete-ready']}.</p>
|
|
140
|
+
<h3>Pruning receipts</h3>
|
|
141
|
+
<ul>${harnessReceipts}</ul>
|
|
142
|
+
<h3>Validation</h3>
|
|
143
|
+
<ul>${harnessErrors}</ul>
|
|
144
|
+
</section>
|
|
145
|
+
</main>
|
|
146
|
+
</body>
|
|
147
|
+
</html>`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function runServer(projectRoot: string, host: string, port: number): Promise<never> {
|
|
151
|
+
const server = Bun.serve({
|
|
152
|
+
hostname: host,
|
|
153
|
+
port,
|
|
154
|
+
async fetch(request) {
|
|
155
|
+
const url = new URL(request.url);
|
|
156
|
+
if (request.method !== 'GET') {
|
|
157
|
+
return Response.json({ error: 'method_not_allowed' }, { status: 405 });
|
|
158
|
+
}
|
|
159
|
+
if (url.pathname === '/api/read-model') {
|
|
160
|
+
return Response.json(await buildConsoleReadModel(projectRoot));
|
|
161
|
+
}
|
|
162
|
+
if (url.pathname !== '/' && url.pathname !== '/index.html') {
|
|
163
|
+
return Response.json({ error: 'not_found' }, { status: 404 });
|
|
164
|
+
}
|
|
165
|
+
const model = await buildConsoleReadModel(projectRoot);
|
|
166
|
+
return new Response(html(model), {
|
|
167
|
+
headers: { 'content-type': 'text/html; charset=utf-8' },
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
console.log(`etiquette console listening on http://${server.hostname}:${server.port}/`);
|
|
172
|
+
console.log('boundary: read-only GET surface; no grants, events, merge, or close');
|
|
173
|
+
return new Promise<never>(() => {
|
|
174
|
+
// Keep the CLI process alive for the HTTP console.
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function dispatchConsole(
|
|
179
|
+
positionals: string[],
|
|
180
|
+
flags: Record<string, string | boolean>,
|
|
181
|
+
): Promise<void> {
|
|
182
|
+
const verb = positionals[1];
|
|
183
|
+
if (!verb || !CONSOLE_VERBS.has(verb)) {
|
|
184
|
+
throw new Error(`console verb must be one of: ${[...CONSOLE_VERBS].join(', ')}`);
|
|
185
|
+
}
|
|
186
|
+
const projectRoot = resolve(optionalString(flags, 'project') ?? process.cwd());
|
|
187
|
+
|
|
188
|
+
if (verb === 'snapshot') {
|
|
189
|
+
const model = await buildConsoleReadModel(projectRoot, optionalString(flags, 'at'));
|
|
190
|
+
console.log(flags.json ? JSON.stringify(model, null, 2) : renderSnapshot(model));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (verb === 'board') {
|
|
195
|
+
console.log(await renderLocalBoard(projectRoot, optionalString(flags, 'seat')));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
await runServer(projectRoot, optionalString(flags, 'host') ?? '127.0.0.1', parsePort(flags.port));
|
|
200
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { optionalString, requireString } from '../argv';
|
|
3
|
+
import { evaluateOperationalDispatch } from '../../dispatch/decision';
|
|
4
|
+
import { recordOperationalDispatch } from '../../dispatch/record';
|
|
5
|
+
import { loadSandboxReceipt } from '../../rubric/loader';
|
|
6
|
+
import { evaluateSeatReadiness, readSeatReadiness } from '../../seat/readiness';
|
|
7
|
+
import { evaluateWorkflowCheck } from '../../workflow/evaluator';
|
|
8
|
+
import { loadWorkflowEnvelope, loadWorkflowMode } from '../../workflow/loader';
|
|
9
|
+
|
|
10
|
+
const CORE_DISPATCH_VERBS = new Set(['check', 'record']);
|
|
11
|
+
|
|
12
|
+
export function isCoreDispatchCommand(command: string): boolean {
|
|
13
|
+
return command === 'dispatch';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function renderDecision(result: ReturnType<typeof evaluateOperationalDispatch>): string {
|
|
17
|
+
return [
|
|
18
|
+
'operational_dispatch_decision:',
|
|
19
|
+
` task_ref: ${result.task_ref}`,
|
|
20
|
+
` seat: ${result.seat}`,
|
|
21
|
+
` decision: ${result.decision}`,
|
|
22
|
+
` reasons: ${result.reasons.join(', ') || 'none'}`,
|
|
23
|
+
` warnings: ${result.warnings.join(', ') || 'none'}`,
|
|
24
|
+
` profile_hint: ${result.profile_hint ?? 'none'}`,
|
|
25
|
+
` seat_routing: ${result.inputs.seat_readiness.routing_recommendation}`,
|
|
26
|
+
` workflow_recommendation: ${result.inputs.workflow_check.recommendation}`,
|
|
27
|
+
` rubric_receipt_accepted: ${String(result.inputs.rubric_receipt.accepted)}`,
|
|
28
|
+
` can_dispatch: ${String(result.authority_boundary.can_dispatch)}`,
|
|
29
|
+
` can_authorize: ${String(result.authority_boundary.can_authorize)}`,
|
|
30
|
+
` consumer_is_engine: ${String(result.authority_boundary.consumer_is_engine)}`,
|
|
31
|
+
].join('\n');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function renderRecord(result: Awaited<ReturnType<typeof recordOperationalDispatch>>): string {
|
|
35
|
+
return [
|
|
36
|
+
'operational_dispatch_record:',
|
|
37
|
+
` record_id: ${result.record.record_id}`,
|
|
38
|
+
` task_ref: ${result.record.task_ref}`,
|
|
39
|
+
` seat: ${result.record.seat}`,
|
|
40
|
+
` decision: ${result.record.decision}`,
|
|
41
|
+
` reasons: ${result.record.reasons.join(', ') || 'none'}`,
|
|
42
|
+
` warnings: ${result.record.warnings.join(', ') || 'none'}`,
|
|
43
|
+
` receipt_ref: ${result.record.receipt_ref}`,
|
|
44
|
+
` ledger_ref: ${result.record.ledger_ref}`,
|
|
45
|
+
` can_execute: ${String(result.record.authority_boundary.can_execute)}`,
|
|
46
|
+
].join('\n');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function dispatchCoreDispatch(
|
|
50
|
+
positionals: string[],
|
|
51
|
+
flags: Record<string, string | boolean>,
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
const verb = positionals[1];
|
|
54
|
+
if (!verb || !CORE_DISPATCH_VERBS.has(verb)) {
|
|
55
|
+
throw new Error(`core dispatch verb must be one of: ${[...CORE_DISPATCH_VERBS].join(', ')}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const json = Boolean(flags.json);
|
|
59
|
+
const projectRoot = resolve(optionalString(flags, 'project') ?? process.cwd());
|
|
60
|
+
const mode = await loadWorkflowMode(resolve(requireString(flags, 'mode')));
|
|
61
|
+
const envelope = await loadWorkflowEnvelope(resolve(requireString(flags, 'task')));
|
|
62
|
+
const readiness = evaluateSeatReadiness(
|
|
63
|
+
await readSeatReadiness(projectRoot, optionalString(flags, 'state')),
|
|
64
|
+
optionalString(flags, 'at'),
|
|
65
|
+
);
|
|
66
|
+
const receiptPath = optionalString(flags, 'sandbox-receipt');
|
|
67
|
+
const sandboxReceipt = receiptPath ? await loadSandboxReceipt(resolve(receiptPath)) : null;
|
|
68
|
+
const result = evaluateOperationalDispatch({
|
|
69
|
+
seat: readiness,
|
|
70
|
+
mode,
|
|
71
|
+
envelope,
|
|
72
|
+
workflow: evaluateWorkflowCheck(mode, envelope),
|
|
73
|
+
sandboxReceipt,
|
|
74
|
+
requestedStrategy: optionalString(flags, 'strategy'),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (verb === 'record') {
|
|
78
|
+
const recorded = await recordOperationalDispatch({
|
|
79
|
+
projectRoot,
|
|
80
|
+
decision: result,
|
|
81
|
+
recordedBy: optionalString(flags, 'recorded-by'),
|
|
82
|
+
at: optionalString(flags, 'at'),
|
|
83
|
+
});
|
|
84
|
+
console.log(json ? JSON.stringify(recorded.record, null, 2) : renderRecord(recorded));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(json ? JSON.stringify(result, null, 2) : renderDecision(result));
|
|
89
|
+
}
|