@etiquekit/etq 1.0.13 → 1.0.15
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 +26 -19
- package/LICENSE +3 -2
- package/NOTICE +4 -6
- package/QuickStart.md +46 -35
- package/README.md +57 -55
- package/bin/etiquette +5 -1
- package/bin/etiquette-core +5 -1
- package/docs/ARCHITECTURE.md +17 -20
- package/docs/CONCEPTS.md +5 -5
- package/docs/CORE_PROFILE.md +15 -7
- package/docs/LANE_PROVISIONING.md +2 -7
- package/docs/README.md +14 -13
- package/docs/RELEASE_SURFACE_AUDIT.md +6 -7
- package/docs/SEAT_DISCIPLINE.md +91 -55
- package/docs/SEAT_PROVISIONING.md +19 -16
- package/docs/TEAM_HANDOFF.md +21 -21
- package/docs/WORKTREE_QOL.md +0 -7
- package/docs/contracts/ledger-entry/README.md +11 -11
- package/docs/contracts/ledger-entry/ledger-entry.v0.2.md +2 -2
- package/docs/contracts/ledger-entry/ledger-entry.v0.md +8 -8
- package/lib/etiquette-core.js +313 -0
- package/lib/etiquette.js +1151 -0
- package/package.json +13 -12
- package/templates/etiquette-vanilla-v0/README.md +3 -2
- package/templates/etiquette-vanilla-v0/source/control-seat/README.md +6 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/access-assurance-check +55 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-doctor +5 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/ACCESS-ASSURANCE.md +39 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/workspace-secure-profile.v0.json +53 -0
- package/templates/etiquette-vanilla-v0/validate-vanilla.sh +5 -0
- package/templates/hosted-receiver/README.md +41 -0
- package/templates/hosted-receiver/w1-github-webhook-receiver.mjs +129 -0
- package/templates/seat-packs-v0/README.md +2 -3
- package/templates/seat-packs-v0/source/claude-code-seat/README.md +3 -3
- package/templates/seat-packs-v0/source/codex-seat/README.md +3 -3
- package/templates/seat-packs-v0/source/gemini-seat/README.md +3 -3
- 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/CONCEPT_STATUS.md +0 -66
- package/packages/control/src/authority/lease.ts +0 -261
- package/packages/control/src/authority/node-delegation.ts +0 -257
- package/packages/control/src/authority/rig-conductor.ts +0 -632
- package/packages/control/src/cli/argv.ts +0 -155
- package/packages/control/src/cli/commands/console.ts +0 -200
- package/packages/control/src/cli/commands/dispatch-core.ts +0 -89
- package/packages/control/src/cli/commands/dispatch.ts +0 -279
- package/packages/control/src/cli/commands/harness.ts +0 -89
- package/packages/control/src/cli/commands/hook.ts +0 -50
- package/packages/control/src/cli/commands/ledger.ts +0 -91
- package/packages/control/src/cli/commands/local-workflow.ts +0 -4686
- package/packages/control/src/cli/commands/memory.ts +0 -445
- package/packages/control/src/cli/commands/release.ts +0 -108
- package/packages/control/src/cli/commands/rubric.ts +0 -103
- package/packages/control/src/cli/commands/seat.ts +0 -179
- package/packages/control/src/cli/commands/session.ts +0 -127
- package/packages/control/src/cli/commands/supervision.ts +0 -246
- package/packages/control/src/cli/commands/sync.ts +0 -119
- package/packages/control/src/cli/commands/workflow.ts +0 -86
- package/packages/control/src/cli/commands/workspace.ts +0 -50
- package/packages/control/src/cli/core-usage.ts +0 -34
- package/packages/control/src/cli/prompt.ts +0 -67
- package/packages/control/src/cli/supervision-deps.ts +0 -44
- package/packages/control/src/cli/usage.ts +0 -241
- package/packages/control/src/cli.ts +0 -207
- package/packages/control/src/core-cli.ts +0 -50
- package/packages/control/src/dispatch/decision.ts +0 -202
- package/packages/control/src/dispatch/projection.ts +0 -293
- package/packages/control/src/dispatch/record.ts +0 -153
- package/packages/control/src/engagement/project.ts +0 -170
- package/packages/control/src/fs.ts +0 -19
- package/packages/control/src/harness/pruning.ts +0 -406
- package/packages/control/src/hooks/dispatcher.ts +0 -117
- package/packages/control/src/hooks/outbox.ts +0 -86
- package/packages/control/src/hooks/sanitize.ts +0 -6
- package/packages/control/src/hooks/types.ts +0 -34
- package/packages/control/src/index.ts +0 -384
- package/packages/control/src/ledger/entry.ts +0 -303
- package/packages/control/src/ledger/indexer.ts +0 -542
- package/packages/control/src/memory/context.ts +0 -149
- package/packages/control/src/memory/drain-import.ts +0 -207
- package/packages/control/src/memory/indexer.ts +0 -284
- package/packages/control/src/memory/query.ts +0 -75
- package/packages/control/src/memory/sanitize.ts +0 -50
- package/packages/control/src/memory/sharded-drain-import.ts +0 -212
- package/packages/control/src/memory/status.ts +0 -211
- package/packages/control/src/memory/store-lifecycle.ts +0 -509
- package/packages/control/src/memory/store.ts +0 -284
- package/packages/control/src/memory/types.ts +0 -146
- package/packages/control/src/parity/surfaces.ts +0 -748
- package/packages/control/src/project.ts +0 -141
- package/packages/control/src/projection/local-ledger-view.ts +0 -373
- package/packages/control/src/projection/return-enforcement.ts +0 -48
- package/packages/control/src/projection/timeline-preview.ts +0 -539
- package/packages/control/src/projection/timeline.ts +0 -708
- package/packages/control/src/release/readiness.ts +0 -831
- package/packages/control/src/rubric/loader.ts +0 -326
- package/packages/control/src/rubric/promotion.ts +0 -54
- package/packages/control/src/rubric/runner.ts +0 -159
- package/packages/control/src/rubric/types.ts +0 -158
- package/packages/control/src/seat/owner-card.ts +0 -388
- package/packages/control/src/seat/readiness.ts +0 -834
- package/packages/control/src/session/runbook.ts +0 -431
- package/packages/control/src/shared/sanitize.ts +0 -49
- package/packages/control/src/supervision/action-classes.ts +0 -192
- package/packages/control/src/supervision/command-apply.ts +0 -378
- package/packages/control/src/supervision/errors.ts +0 -14
- package/packages/control/src/supervision/event-replay.ts +0 -155
- package/packages/control/src/supervision/events.ts +0 -109
- package/packages/control/src/supervision/index.ts +0 -16
- package/packages/control/src/supervision/manifest.ts +0 -127
- package/packages/control/src/supervision/paths.ts +0 -49
- package/packages/control/src/supervision/projection-adapter.ts +0 -274
- package/packages/control/src/supervision/projection.ts +0 -75
- package/packages/control/src/supervision/rebuild.ts +0 -99
- package/packages/control/src/supervision/session-open.ts +0 -131
- package/packages/control/src/supervision/session-read.ts +0 -99
- package/packages/control/src/supervision/sqlite-impl.ts +0 -71
- package/packages/control/src/supervision/sqlite.ts +0 -121
- package/packages/control/src/supervision/store-rows.ts +0 -371
- package/packages/control/src/supervision/turn-close.ts +0 -154
- package/packages/control/src/supervision/turn-open.ts +0 -284
- package/packages/control/src/sync/event-log-merge-driver.ts +0 -263
- package/packages/control/src/sync/join-plan.ts +0 -375
- package/packages/control/src/sync/outbox.ts +0 -492
- package/packages/control/src/workflow/evaluator.ts +0 -140
- package/packages/control/src/workflow/loader.ts +0 -200
- package/packages/control/src/workflow/types.ts +0 -90
- package/packages/control/src/workspace/authority.ts +0 -499
- package/packages/protocol/src/guards.ts +0 -119
- package/packages/protocol/src/huddle-board.ts +0 -198
- package/packages/protocol/src/huddle.ts +0 -295
- package/packages/protocol/src/incident.ts +0 -251
- package/packages/protocol/src/index.ts +0 -8
- package/packages/protocol/src/interfaces.ts +0 -107
- package/packages/protocol/src/packet-profile.ts +0 -195
- package/packages/protocol/src/state.ts +0 -81
- package/packages/protocol/src/types.ts +0 -434
- package/release/lineage.v0.json +0 -14
- package/scripts/release-candidate-verify.sh +0 -175
- package/scripts/release-checksum.sh +0 -25
- package/scripts/release-pack-canary.sh +0 -97
- package/scripts/release-scan.sh +0 -249
- package/scripts/release-sign.sh +0 -34
|
@@ -1,539 +0,0 @@
|
|
|
1
|
-
import { applyTimelineDeepLink, type TimelineFeedItem, type TimelineItemType, type TimelineView } from './timeline';
|
|
2
|
-
|
|
3
|
-
export const TIMELINE_PREVIEW_ALLOWED_METHODS = ['GET', 'HEAD', 'OPTIONS'] as const;
|
|
4
|
-
|
|
5
|
-
function escapeHtml(value: string): string {
|
|
6
|
-
return value
|
|
7
|
-
.replace(/&/g, '&')
|
|
8
|
-
.replace(/</g, '<')
|
|
9
|
-
.replace(/>/g, '>')
|
|
10
|
-
.replace(/"/g, '"')
|
|
11
|
-
.replace(/'/g, ''');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function titleCase(value: string): string {
|
|
15
|
-
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function itemBadge(itemType: TimelineItemType): string {
|
|
19
|
-
switch (itemType) {
|
|
20
|
-
case 'task_card':
|
|
21
|
-
return 'TASK';
|
|
22
|
-
case 'return':
|
|
23
|
-
return 'RETURN';
|
|
24
|
-
case 'receipt':
|
|
25
|
-
return 'RECEIPT';
|
|
26
|
-
case 'decision':
|
|
27
|
-
return 'DECISION';
|
|
28
|
-
case 'chatter':
|
|
29
|
-
return 'CHATTER';
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function itemTone(itemType: TimelineItemType): string {
|
|
34
|
-
switch (itemType) {
|
|
35
|
-
case 'task_card':
|
|
36
|
-
return 'task';
|
|
37
|
-
case 'return':
|
|
38
|
-
return 'return';
|
|
39
|
-
case 'receipt':
|
|
40
|
-
return 'receipt';
|
|
41
|
-
case 'decision':
|
|
42
|
-
return 'decision';
|
|
43
|
-
case 'chatter':
|
|
44
|
-
return 'chatter';
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function sourceRef(item: TimelineFeedItem): string {
|
|
49
|
-
return item.source.event_ref ?? item.source.packet_ref ?? 'unsequenced';
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function participants(item: TimelineFeedItem): string {
|
|
53
|
-
const parts = [
|
|
54
|
-
item.actor_seat_id ? `from ${item.actor_seat_id}` : null,
|
|
55
|
-
item.seat_id ? `seat ${item.seat_id}` : null,
|
|
56
|
-
item.operator_principal ? `operator ${item.operator_principal}` : null,
|
|
57
|
-
].filter((part): part is string => Boolean(part));
|
|
58
|
-
return parts.length > 0 ? parts.join(' / ') : 'seat unresolved';
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function chip(label: string, value: string | null, className = ''): string {
|
|
62
|
-
return value ? `<span class="chip ${escapeHtml(className)}"><strong>${escapeHtml(label)}</strong>${escapeHtml(value)}</span>` : '';
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function renderSections(item: TimelineFeedItem): string {
|
|
66
|
-
return `<div class="sections" role="tablist" aria-label="Card sections">
|
|
67
|
-
${item.display.sections.map((section, index) => `<section class="card-section" role="tabpanel" aria-labelledby="${escapeHtml(item.item_id)}-tab-${index}">
|
|
68
|
-
<button id="${escapeHtml(item.item_id)}-tab-${index}" type="button" class="section-tab">${escapeHtml(section.label)}</button>
|
|
69
|
-
<p>${escapeHtml(section.body)}</p>
|
|
70
|
-
</section>`).join('\n')}
|
|
71
|
-
</div>`;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function renderItem(item: TimelineFeedItem): string {
|
|
75
|
-
const tone = itemTone(item.item_type);
|
|
76
|
-
const seqAnchor = item.source.seq === null ? escapeHtml(item.item_id) : `seq-${String(item.source.seq)}`;
|
|
77
|
-
return `<article id="${seqAnchor}" class="feed-item ${tone}" data-item-type="${escapeHtml(item.item_type)}" data-thread-id="${escapeHtml(item.thread.thread_id)}" data-depth="${String(item.thread.depth)}">
|
|
78
|
-
<div class="item-head">
|
|
79
|
-
<span class="badge ${tone}">${escapeHtml(itemBadge(item.item_type))}</span>
|
|
80
|
-
<time>${escapeHtml(item.created_at ?? 'time unknown')}</time>
|
|
81
|
-
</div>
|
|
82
|
-
<div class="chips" aria-label="Timeline metadata">
|
|
83
|
-
${chip('seat', item.display.seat_badge, 'seat')}
|
|
84
|
-
${chip('role', item.display.role_chip, 'role')}
|
|
85
|
-
${chip('lane', item.display.lane_chip, 'lane')}
|
|
86
|
-
${chip('verdict', item.display.verdict_pill, 'verdict-pill')}
|
|
87
|
-
${chip('status', item.display.status_subtitle, 'status')}
|
|
88
|
-
${item.display.topic_chips.map((topic) => chip('topic', topic, 'topic')).join('')}
|
|
89
|
-
${item.requires_ack === true ? '<span class="chip ack"><strong>ack</strong>requires_ack</span>' : ''}
|
|
90
|
-
</div>
|
|
91
|
-
<h2>${escapeHtml(item.title)}</h2>
|
|
92
|
-
<p class="body">${escapeHtml(item.body)}</p>
|
|
93
|
-
${renderSections(item)}
|
|
94
|
-
<dl>
|
|
95
|
-
<div><dt>Source</dt><dd><code>${escapeHtml(sourceRef(item))}</code></dd></div>
|
|
96
|
-
<div><dt>Participants</dt><dd>${escapeHtml(participants(item))}</dd></div>
|
|
97
|
-
${item.lane ? `<div><dt>Lane</dt><dd><code>${escapeHtml(item.lane)}</code></dd></div>` : ''}
|
|
98
|
-
<div><dt>Thread</dt><dd><code>${escapeHtml(item.thread.thread_id)}</code> - ${String(item.thread.thread_item_count)} item${item.thread.thread_item_count === 1 ? '' : 's'}</dd></div>
|
|
99
|
-
<div><dt>Authority</dt><dd>projection only; cannot execute, write back, grant, close, merge, or sign</dd></div>
|
|
100
|
-
</dl>
|
|
101
|
-
</article>`;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function renderThreadIndex(view: TimelineView): string {
|
|
105
|
-
if (view.feed.threads.length === 0) return '<section class="empty">No threads found for this scope.</section>';
|
|
106
|
-
return `<ol class="thread-index">
|
|
107
|
-
${view.feed.threads.map((thread) => `<li>
|
|
108
|
-
<a href="#${escapeHtml(thread.latest_seq === null ? thread.item_ids[0] : `seq-${thread.latest_seq}`)}">${escapeHtml(thread.title)}</a>
|
|
109
|
-
<span>${String(thread.item_count)} items</span>
|
|
110
|
-
</li>`).join('\n')}
|
|
111
|
-
</ol>`;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function renderCuratorPortal(view: TimelineView): string {
|
|
115
|
-
const seats = view.curator_portal.health_board.seats.length > 0
|
|
116
|
-
? view.curator_portal.health_board.seats.map((seat) => `<tr>
|
|
117
|
-
<td><code>${escapeHtml(seat.seat_id)}</code></td>
|
|
118
|
-
<td>${escapeHtml(seat.lifecycle_state)}</td>
|
|
119
|
-
<td>${seat.ready ? 'ready' : 'not ready'}</td>
|
|
120
|
-
<td>${escapeHtml(seat.source)}</td>
|
|
121
|
-
<td><code>${escapeHtml(seat.sentinel_state_ref)}</code></td>
|
|
122
|
-
</tr>`).join('\n')
|
|
123
|
-
: '<tr><td colspan="5">No seats in this projection.</td></tr>';
|
|
124
|
-
const queue = view.curator_portal.promotion_queue.items.length > 0
|
|
125
|
-
? view.curator_portal.promotion_queue.items.map((item) => `<li><code>${escapeHtml(item.source_ref)}</code> ${escapeHtml(item.reason)} ${item.next_owner ? `- next ${escapeHtml(item.next_owner)}` : ''}</li>`).join('\n')
|
|
126
|
-
: '<li>No display-only queue items.</li>';
|
|
127
|
-
return `<section class="curator" aria-label="Curator Portal">
|
|
128
|
-
<div class="section-title">
|
|
129
|
-
<h2>Curator Portal</h2>
|
|
130
|
-
<span>display-only</span>
|
|
131
|
-
</div>
|
|
132
|
-
<div class="curator-grid">
|
|
133
|
-
<section>
|
|
134
|
-
<h3>Health board</h3>
|
|
135
|
-
<table>
|
|
136
|
-
<thead><tr><th>Seat</th><th>Lifecycle</th><th>Ready</th><th>Source</th><th>Sentinel</th></tr></thead>
|
|
137
|
-
<tbody>${seats}</tbody>
|
|
138
|
-
</table>
|
|
139
|
-
</section>
|
|
140
|
-
<section>
|
|
141
|
-
<h3>Digest view</h3>
|
|
142
|
-
<dl>
|
|
143
|
-
<div><dt>Threads</dt><dd>${String(view.curator_portal.digest_view.threads)}</dd></div>
|
|
144
|
-
<div><dt>Requires ack</dt><dd>${String(view.curator_portal.digest_view.requires_ack)}</dd></div>
|
|
145
|
-
<div><dt>Tasks</dt><dd>${String(view.curator_portal.digest_view.items_by_type.task_card)}</dd></div>
|
|
146
|
-
<div><dt>Returns</dt><dd>${String(view.curator_portal.digest_view.items_by_type.return)}</dd></div>
|
|
147
|
-
</dl>
|
|
148
|
-
</section>
|
|
149
|
-
<section>
|
|
150
|
-
<h3>Promotion queue</h3>
|
|
151
|
-
<p>Display-only; cannot promote, grant, close, merge, execute, or write back.</p>
|
|
152
|
-
<ul>${queue}</ul>
|
|
153
|
-
</section>
|
|
154
|
-
</div>
|
|
155
|
-
</section>`;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export function renderTimelinePreviewHtml(view: TimelineView): string {
|
|
159
|
-
const items = view.feed.items.length > 0
|
|
160
|
-
? view.feed.items.map((item) => renderItem(item)).join('\n')
|
|
161
|
-
: '<section class="empty">No timeline items found for this scope.</section>';
|
|
162
|
-
const selected = view.feed.deep_link.item_id ? `Selected ${view.feed.deep_link.event_ref ?? view.feed.deep_link.item_id}` : 'No deep link selected';
|
|
163
|
-
const scopeLabel = titleCase(view.scope);
|
|
164
|
-
return `<!doctype html>
|
|
165
|
-
<html lang="en">
|
|
166
|
-
<head>
|
|
167
|
-
<meta charset="utf-8">
|
|
168
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
169
|
-
<title>Etiquette Timeline Preview</title>
|
|
170
|
-
<style>
|
|
171
|
-
:root {
|
|
172
|
-
color-scheme: light;
|
|
173
|
-
--ink: #172026;
|
|
174
|
-
--muted: #5d6973;
|
|
175
|
-
--line: #d7dee5;
|
|
176
|
-
--surface: #ffffff;
|
|
177
|
-
--page: #f6f7f8;
|
|
178
|
-
--teal: #0f766e;
|
|
179
|
-
--amber: #a15c00;
|
|
180
|
-
--red: #b42318;
|
|
181
|
-
--blue: #2563eb;
|
|
182
|
-
--violet: #6d28d9;
|
|
183
|
-
}
|
|
184
|
-
* { box-sizing: border-box; }
|
|
185
|
-
body {
|
|
186
|
-
margin: 0;
|
|
187
|
-
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
188
|
-
color: var(--ink);
|
|
189
|
-
background: var(--page);
|
|
190
|
-
}
|
|
191
|
-
main { width: min(1120px, calc(100% - 32px)); margin: 0 auto; padding: 24px 0 40px; }
|
|
192
|
-
header {
|
|
193
|
-
display: grid;
|
|
194
|
-
grid-template-columns: minmax(0, 1fr) auto;
|
|
195
|
-
gap: 16px;
|
|
196
|
-
align-items: end;
|
|
197
|
-
padding: 18px 0 16px;
|
|
198
|
-
border-bottom: 1px solid var(--line);
|
|
199
|
-
}
|
|
200
|
-
h1 { margin: 0; font-size: 28px; line-height: 1.15; letter-spacing: 0; }
|
|
201
|
-
.subhead { margin: 6px 0 0; color: var(--muted); line-height: 1.45; }
|
|
202
|
-
.scope {
|
|
203
|
-
display: inline-flex;
|
|
204
|
-
align-items: center;
|
|
205
|
-
min-height: 32px;
|
|
206
|
-
padding: 6px 10px;
|
|
207
|
-
border-radius: 8px;
|
|
208
|
-
background: #e6f4f1;
|
|
209
|
-
color: #075e57;
|
|
210
|
-
font-weight: 700;
|
|
211
|
-
white-space: nowrap;
|
|
212
|
-
}
|
|
213
|
-
.summary {
|
|
214
|
-
display: grid;
|
|
215
|
-
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
216
|
-
gap: 10px;
|
|
217
|
-
margin: 18px 0;
|
|
218
|
-
}
|
|
219
|
-
.metric {
|
|
220
|
-
background: var(--surface);
|
|
221
|
-
border: 1px solid var(--line);
|
|
222
|
-
border-radius: 8px;
|
|
223
|
-
padding: 12px;
|
|
224
|
-
min-width: 0;
|
|
225
|
-
}
|
|
226
|
-
.metric span { display: block; color: var(--muted); font-size: 12px; }
|
|
227
|
-
.metric strong { display: block; margin-top: 4px; font-size: 18px; overflow-wrap: anywhere; }
|
|
228
|
-
.boundary {
|
|
229
|
-
display: grid;
|
|
230
|
-
gap: 8px;
|
|
231
|
-
margin: 18px 0;
|
|
232
|
-
padding: 14px;
|
|
233
|
-
border: 1px solid #f0c36d;
|
|
234
|
-
border-left: 4px solid var(--amber);
|
|
235
|
-
border-radius: 8px;
|
|
236
|
-
background: #fff8eb;
|
|
237
|
-
}
|
|
238
|
-
.boundary strong { color: #7a4100; }
|
|
239
|
-
.view-controls {
|
|
240
|
-
display: flex;
|
|
241
|
-
flex-wrap: wrap;
|
|
242
|
-
align-items: center;
|
|
243
|
-
justify-content: space-between;
|
|
244
|
-
gap: 10px;
|
|
245
|
-
margin: 18px 0 10px;
|
|
246
|
-
}
|
|
247
|
-
.toggle {
|
|
248
|
-
display: inline-flex;
|
|
249
|
-
overflow: hidden;
|
|
250
|
-
border: 1px solid var(--line);
|
|
251
|
-
border-radius: 8px;
|
|
252
|
-
background: var(--surface);
|
|
253
|
-
}
|
|
254
|
-
.toggle a {
|
|
255
|
-
min-height: 34px;
|
|
256
|
-
padding: 8px 12px;
|
|
257
|
-
border-right: 1px solid var(--line);
|
|
258
|
-
color: var(--ink);
|
|
259
|
-
text-decoration: none;
|
|
260
|
-
font-weight: 700;
|
|
261
|
-
}
|
|
262
|
-
.toggle a:last-child { border-right: 0; }
|
|
263
|
-
.toggle a[aria-current="true"] { background: #e6f4f1; color: #075e57; }
|
|
264
|
-
.thread-index {
|
|
265
|
-
display: grid;
|
|
266
|
-
gap: 8px;
|
|
267
|
-
margin: 0 0 18px;
|
|
268
|
-
padding: 0;
|
|
269
|
-
list-style: none;
|
|
270
|
-
}
|
|
271
|
-
.thread-index li {
|
|
272
|
-
display: flex;
|
|
273
|
-
gap: 10px;
|
|
274
|
-
justify-content: space-between;
|
|
275
|
-
padding: 10px 12px;
|
|
276
|
-
border: 1px solid var(--line);
|
|
277
|
-
border-radius: 8px;
|
|
278
|
-
background: var(--surface);
|
|
279
|
-
}
|
|
280
|
-
.thread-index a { color: var(--blue); font-weight: 700; overflow-wrap: anywhere; }
|
|
281
|
-
.feed { display: grid; gap: 12px; margin-top: 18px; }
|
|
282
|
-
.feed-item {
|
|
283
|
-
background: var(--surface);
|
|
284
|
-
border: 1px solid var(--line);
|
|
285
|
-
border-left: 4px solid var(--teal);
|
|
286
|
-
border-radius: 8px;
|
|
287
|
-
padding: 14px;
|
|
288
|
-
}
|
|
289
|
-
.feed-item.return { border-left-color: var(--blue); }
|
|
290
|
-
.feed-item.receipt { border-left-color: var(--violet); }
|
|
291
|
-
.feed-item.decision { border-left-color: var(--red); }
|
|
292
|
-
.feed-item.chatter { border-left-color: #64748b; }
|
|
293
|
-
.item-head { display: flex; gap: 10px; align-items: center; justify-content: space-between; }
|
|
294
|
-
.badge {
|
|
295
|
-
display: inline-flex;
|
|
296
|
-
min-height: 24px;
|
|
297
|
-
align-items: center;
|
|
298
|
-
padding: 3px 8px;
|
|
299
|
-
border-radius: 6px;
|
|
300
|
-
background: #e6f4f1;
|
|
301
|
-
color: #075e57;
|
|
302
|
-
font-size: 12px;
|
|
303
|
-
font-weight: 800;
|
|
304
|
-
}
|
|
305
|
-
.badge.return { background: #eaf1ff; color: #1d4ed8; }
|
|
306
|
-
.badge.receipt { background: #f1eafe; color: #5b21b6; }
|
|
307
|
-
.badge.decision { background: #feeceb; color: #991b1b; }
|
|
308
|
-
.badge.chatter { background: #eef2f5; color: #475569; }
|
|
309
|
-
.chips {
|
|
310
|
-
display: flex;
|
|
311
|
-
flex-wrap: wrap;
|
|
312
|
-
gap: 6px;
|
|
313
|
-
margin-top: 10px;
|
|
314
|
-
}
|
|
315
|
-
.chip {
|
|
316
|
-
display: inline-flex;
|
|
317
|
-
gap: 6px;
|
|
318
|
-
align-items: center;
|
|
319
|
-
min-height: 24px;
|
|
320
|
-
padding: 3px 8px;
|
|
321
|
-
border: 1px solid #d5dde6;
|
|
322
|
-
border-radius: 999px;
|
|
323
|
-
background: #f8fafc;
|
|
324
|
-
color: #334155;
|
|
325
|
-
font-size: 12px;
|
|
326
|
-
}
|
|
327
|
-
.chip strong { color: #64748b; font-weight: 800; }
|
|
328
|
-
.chip.verdict-pill { border-color: #bfd1ff; background: #eaf1ff; color: #1d4ed8; }
|
|
329
|
-
.chip.ack { border-color: #f5c2c0; background: #feeceb; color: #991b1b; }
|
|
330
|
-
time { color: var(--muted); font-size: 12px; text-align: right; }
|
|
331
|
-
h2 { margin: 10px 0 6px; font-size: 17px; letter-spacing: 0; line-height: 1.25; }
|
|
332
|
-
.body { margin: 0 0 12px; color: #334155; overflow-wrap: anywhere; }
|
|
333
|
-
.sections {
|
|
334
|
-
display: grid;
|
|
335
|
-
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
|
|
336
|
-
gap: 8px;
|
|
337
|
-
margin: 12px 0;
|
|
338
|
-
}
|
|
339
|
-
.card-section {
|
|
340
|
-
padding: 8px;
|
|
341
|
-
border: 1px solid var(--line);
|
|
342
|
-
border-radius: 8px;
|
|
343
|
-
background: #fbfcfd;
|
|
344
|
-
}
|
|
345
|
-
.section-tab {
|
|
346
|
-
border: 0;
|
|
347
|
-
padding: 0;
|
|
348
|
-
background: transparent;
|
|
349
|
-
color: var(--ink);
|
|
350
|
-
font-weight: 800;
|
|
351
|
-
text-align: left;
|
|
352
|
-
}
|
|
353
|
-
.card-section p { margin: 6px 0 0; color: var(--muted); font-size: 13px; line-height: 1.35; }
|
|
354
|
-
dl {
|
|
355
|
-
display: grid;
|
|
356
|
-
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
357
|
-
gap: 8px 14px;
|
|
358
|
-
margin: 0;
|
|
359
|
-
}
|
|
360
|
-
dl div { min-width: 0; }
|
|
361
|
-
dt { color: var(--muted); font-size: 12px; }
|
|
362
|
-
dd { margin: 2px 0 0; overflow-wrap: anywhere; }
|
|
363
|
-
code {
|
|
364
|
-
padding: 2px 5px;
|
|
365
|
-
border-radius: 4px;
|
|
366
|
-
background: #eef2f5;
|
|
367
|
-
color: #263238;
|
|
368
|
-
font-size: 12px;
|
|
369
|
-
}
|
|
370
|
-
.empty {
|
|
371
|
-
padding: 20px;
|
|
372
|
-
border: 1px solid var(--line);
|
|
373
|
-
border-radius: 8px;
|
|
374
|
-
background: var(--surface);
|
|
375
|
-
color: var(--muted);
|
|
376
|
-
}
|
|
377
|
-
.curator {
|
|
378
|
-
margin-top: 22px;
|
|
379
|
-
padding-top: 18px;
|
|
380
|
-
border-top: 1px solid var(--line);
|
|
381
|
-
}
|
|
382
|
-
.section-title {
|
|
383
|
-
display: flex;
|
|
384
|
-
align-items: center;
|
|
385
|
-
justify-content: space-between;
|
|
386
|
-
gap: 12px;
|
|
387
|
-
margin-bottom: 10px;
|
|
388
|
-
}
|
|
389
|
-
.section-title h2 { margin: 0; }
|
|
390
|
-
.section-title span {
|
|
391
|
-
padding: 4px 8px;
|
|
392
|
-
border-radius: 999px;
|
|
393
|
-
background: #eef2f5;
|
|
394
|
-
color: #475569;
|
|
395
|
-
font-size: 12px;
|
|
396
|
-
font-weight: 800;
|
|
397
|
-
}
|
|
398
|
-
.curator-grid {
|
|
399
|
-
display: grid;
|
|
400
|
-
grid-template-columns: minmax(0, 1.4fr) minmax(220px, .8fr);
|
|
401
|
-
gap: 12px;
|
|
402
|
-
}
|
|
403
|
-
.curator-grid section {
|
|
404
|
-
min-width: 0;
|
|
405
|
-
padding: 12px;
|
|
406
|
-
border: 1px solid var(--line);
|
|
407
|
-
border-radius: 8px;
|
|
408
|
-
background: var(--surface);
|
|
409
|
-
}
|
|
410
|
-
.curator-grid section:first-child { grid-row: span 2; }
|
|
411
|
-
.curator h3 { margin: 0 0 8px; font-size: 16px; }
|
|
412
|
-
table {
|
|
413
|
-
width: 100%;
|
|
414
|
-
border-collapse: collapse;
|
|
415
|
-
font-size: 13px;
|
|
416
|
-
}
|
|
417
|
-
th, td {
|
|
418
|
-
padding: 7px 6px;
|
|
419
|
-
border-bottom: 1px solid var(--line);
|
|
420
|
-
text-align: left;
|
|
421
|
-
vertical-align: top;
|
|
422
|
-
overflow-wrap: anywhere;
|
|
423
|
-
}
|
|
424
|
-
th { color: var(--muted); font-size: 12px; }
|
|
425
|
-
ul { margin: 8px 0 0; padding-left: 18px; }
|
|
426
|
-
@media (max-width: 640px) {
|
|
427
|
-
main { width: min(100% - 20px, 1120px); padding-top: 12px; }
|
|
428
|
-
header { grid-template-columns: 1fr; align-items: start; }
|
|
429
|
-
.scope { justify-self: start; }
|
|
430
|
-
.item-head { align-items: flex-start; flex-direction: column; }
|
|
431
|
-
time { text-align: left; }
|
|
432
|
-
.curator-grid { grid-template-columns: 1fr; }
|
|
433
|
-
}
|
|
434
|
-
</style>
|
|
435
|
-
</head>
|
|
436
|
-
<body data-authority="projection-only" data-ledger-write-path="false">
|
|
437
|
-
<main>
|
|
438
|
-
<header>
|
|
439
|
-
<div>
|
|
440
|
-
<h1>Etiquette Timeline Preview</h1>
|
|
441
|
-
<p class="subhead">Read-only loopback panel over the existing timeline projection.</p>
|
|
442
|
-
</div>
|
|
443
|
-
<span class="scope">${escapeHtml(scopeLabel)}</span>
|
|
444
|
-
</header>
|
|
445
|
-
<section class="summary" aria-label="Projection summary">
|
|
446
|
-
<div class="metric"><span>Schema</span><strong>${escapeHtml(view.schema)}</strong></div>
|
|
447
|
-
<div class="metric"><span>Items</span><strong>${String(view.feed.items_matched)}</strong></div>
|
|
448
|
-
<div class="metric"><span>Source</span><strong>${escapeHtml(view.source.projection_schema)}</strong></div>
|
|
449
|
-
<div class="metric"><span>Watermark</span><strong>${escapeHtml(view.watermark.latest_event_ref)}</strong></div>
|
|
450
|
-
<div class="metric"><span>Commit</span><strong>${escapeHtml(view.watermark.commit_sha)}</strong></div>
|
|
451
|
-
<div class="metric"><span>Event log hash</span><strong>${escapeHtml(view.watermark.event_log_hash)}</strong></div>
|
|
452
|
-
</section>
|
|
453
|
-
<section class="boundary">
|
|
454
|
-
<strong>Boundary</strong>
|
|
455
|
-
<span>${escapeHtml(view.boundary)}</span>
|
|
456
|
-
<span>GET/HEAD/OPTIONS only; no hosted backend, no auth provider, no impersonation, no execution, no write-back.</span>
|
|
457
|
-
</section>
|
|
458
|
-
<section class="view-controls" aria-label="Feed modes">
|
|
459
|
-
<nav class="toggle" aria-label="Timeline display modes">
|
|
460
|
-
<a href="#stream" aria-current="true">Stream</a>
|
|
461
|
-
<a href="#threads">Thread</a>
|
|
462
|
-
</nav>
|
|
463
|
-
<span>${escapeHtml(selected)}</span>
|
|
464
|
-
</section>
|
|
465
|
-
<section id="threads" aria-label="Thread index">
|
|
466
|
-
${renderThreadIndex(view)}
|
|
467
|
-
</section>
|
|
468
|
-
<section id="stream" class="feed" aria-label="Timeline feed">
|
|
469
|
-
${items}
|
|
470
|
-
</section>
|
|
471
|
-
${renderCuratorPortal(view)}
|
|
472
|
-
</main>
|
|
473
|
-
</body>
|
|
474
|
-
</html>`;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
function previewHeaders(contentType: string): Headers {
|
|
478
|
-
return new Headers({
|
|
479
|
-
'allow': TIMELINE_PREVIEW_ALLOWED_METHODS.join(', '),
|
|
480
|
-
'cache-control': 'no-store',
|
|
481
|
-
'content-type': contentType,
|
|
482
|
-
'x-etiquette-authority': 'projection-only',
|
|
483
|
-
'x-etiquette-ledger-write-path': 'false',
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
function emptyIfHead(request: Request, body: string): string | null {
|
|
488
|
-
return request.method === 'HEAD' ? null : body;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
export async function timelinePreviewResponseForRequest(
|
|
492
|
-
request: Request,
|
|
493
|
-
buildView: () => Promise<TimelineView>,
|
|
494
|
-
): Promise<Response> {
|
|
495
|
-
if (request.method === 'OPTIONS') {
|
|
496
|
-
return new Response(null, { status: 204, headers: previewHeaders('text/plain; charset=utf-8') });
|
|
497
|
-
}
|
|
498
|
-
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
|
499
|
-
return Response.json(
|
|
500
|
-
{
|
|
501
|
-
error: 'method_not_allowed',
|
|
502
|
-
allowed_methods: TIMELINE_PREVIEW_ALLOWED_METHODS,
|
|
503
|
-
authority_boundary: {
|
|
504
|
-
request_can_write_ledger: false,
|
|
505
|
-
request_can_execute: false,
|
|
506
|
-
request_can_grant: false,
|
|
507
|
-
},
|
|
508
|
-
},
|
|
509
|
-
{ status: 405, headers: previewHeaders('application/json; charset=utf-8') },
|
|
510
|
-
);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
const url = new URL(request.url);
|
|
514
|
-
const requestedSeq = Number.parseInt(url.searchParams.get('seq') ?? '', 10);
|
|
515
|
-
const selectSeq = Number.isFinite(requestedSeq) ? requestedSeq : null;
|
|
516
|
-
if (url.pathname === '/api/timeline') {
|
|
517
|
-
const view = applyTimelineDeepLink(await buildView(), selectSeq);
|
|
518
|
-
return new Response(emptyIfHead(request, JSON.stringify(view, null, 2)), {
|
|
519
|
-
headers: previewHeaders('application/json; charset=utf-8'),
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
if (url.pathname === '/' || url.pathname === '/index.html') {
|
|
523
|
-
const view = applyTimelineDeepLink(await buildView(), selectSeq);
|
|
524
|
-
return new Response(emptyIfHead(request, renderTimelinePreviewHtml(view)), {
|
|
525
|
-
headers: previewHeaders('text/html; charset=utf-8'),
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
return Response.json(
|
|
529
|
-
{
|
|
530
|
-
error: 'not_found',
|
|
531
|
-
authority_boundary: {
|
|
532
|
-
request_can_write_ledger: false,
|
|
533
|
-
request_can_execute: false,
|
|
534
|
-
request_can_grant: false,
|
|
535
|
-
},
|
|
536
|
-
},
|
|
537
|
-
{ status: 404, headers: previewHeaders('application/json; charset=utf-8') },
|
|
538
|
-
);
|
|
539
|
-
}
|