@mnstry/atelier 0.2.0-alpha.4 → 0.2.0-alpha.5
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/CHANGELOG.md +62 -0
- package/README.md +38 -12
- package/contracts/public-api-baseline.json +57 -0
- package/docs/assurance-controls.md +39 -0
- package/docs/atelier-runtime.md +15 -0
- package/docs/blocks/claims.md +15 -9
- package/docs/design.md +12 -6
- package/docs/install.md +26 -4
- package/docs/knowledge-graph.md +8 -4
- package/docs/local-services.md +101 -0
- package/docs/release-engineering.md +75 -10
- package/docs/repo-boundary-guard.md +12 -2
- package/docs/upgrade.md +25 -2
- package/fixtures/projects/sample-workspace/content/source.html.kg.json +4 -1
- package/fixtures/projects/source-formats-workspace/content/data.json.kg.json +4 -1
- package/fixtures/projects/source-formats-workspace/content/logo.png.kg.json +4 -1
- package/fixtures/projects/source-formats-workspace/content/metrics.csv.kg.json +4 -1
- package/fixtures/projects/source-formats-workspace/content/pipeline.yaml.kg.json +4 -1
- package/package.json +12 -5
- package/skills/claude/atelier-local-service/SKILL.md +47 -0
- package/skills/claude/atelier-public-boundary/SKILL.md +31 -0
- package/skills/codex/atelier-local-service/SKILL.md +47 -0
- package/skills/codex/atelier-public-boundary/SKILL.md +31 -0
- package/src/boundary/content-rules.mjs +278 -20
- package/src/boundary/policy.mjs +150 -60
- package/src/cli/execute-command.mjs +36 -0
- package/src/cli/run.mjs +17 -7
- package/src/collaboration/event-ledger.mjs +365 -0
- package/src/collaboration/index.mjs +17 -0
- package/src/collaboration/proposals.mjs +265 -65
- package/src/commands/attestation.mjs +20 -6
- package/src/commands/disclosure.mjs +133 -0
- package/src/commands/distribution.mjs +2 -1
- package/src/commands/extension-pack.mjs +2 -1
- package/src/commands/init.mjs +2 -1
- package/src/commands/server.mjs +1 -4
- package/src/disclosure/content-scan.mjs +193 -0
- package/src/egress/check.mjs +7 -38
- package/src/egress/forbidden-egress.mjs +32 -18
- package/src/graph/graph.mjs +112 -314
- package/src/graph/knowledge-graph.mjs +94 -18
- package/src/harness/context-client.mjs +9 -1
- package/src/index.mjs +12 -0
- package/src/project/config.mjs +66 -7
- package/src/project/file-class.mjs +14 -0
- package/src/project/package-root.mjs +10 -0
- package/src/project/path-match.mjs +38 -15
- package/src/project/private-state.mjs +110 -0
- package/src/server/local-sidecar.mjs +81 -59
- package/src/server/security.mjs +89 -4
- package/src/server/server.mjs +3 -2
- package/src/support/feedback-report.mjs +4 -3
- package/src/upgrade/upgrade.mjs +2 -1
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import crypto from 'node:crypto'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { canonicalize } from '../attestation/jcs.mjs'
|
|
5
|
+
import {
|
|
6
|
+
atomicReplacePrivateText,
|
|
7
|
+
ensureContainedPrivateDirectory,
|
|
8
|
+
openRegularFileNoFollow,
|
|
9
|
+
} from '../project/private-state.mjs'
|
|
10
|
+
|
|
11
|
+
export const ATELIER_COLLABORATION_EVENT_SCHEMA = 'atelier-collaboration-event@v1'
|
|
12
|
+
export const COLLABORATION_LEDGER_LIMITS = Object.freeze({
|
|
13
|
+
maxLineBytes: 256 * 1024,
|
|
14
|
+
maxBytes: 16 * 1024 * 1024,
|
|
15
|
+
maxEvents: 10_000,
|
|
16
|
+
retainPerAggregate: 50,
|
|
17
|
+
retainDays: 180,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
function isRecord(value) {
|
|
21
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function validTimestamp(value) {
|
|
25
|
+
return typeof value === 'string' && Number.isFinite(Date.parse(value))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function secureAppendLine(file, line) {
|
|
29
|
+
const descriptor = openRegularFileNoFollow(
|
|
30
|
+
file,
|
|
31
|
+
fs.constants.O_WRONLY | fs.constants.O_APPEND | fs.constants.O_CREAT,
|
|
32
|
+
0o600,
|
|
33
|
+
)
|
|
34
|
+
try {
|
|
35
|
+
fs.writeFileSync(descriptor, line)
|
|
36
|
+
fs.fsyncSync(descriptor)
|
|
37
|
+
fs.fchmodSync(descriptor, 0o600)
|
|
38
|
+
} finally {
|
|
39
|
+
fs.closeSync(descriptor)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function eventId(event) {
|
|
44
|
+
const authoritative = {
|
|
45
|
+
schema: event.schema,
|
|
46
|
+
aggregateId: event.aggregateId,
|
|
47
|
+
version: event.version,
|
|
48
|
+
type: event.type,
|
|
49
|
+
actor: event.actor,
|
|
50
|
+
at: event.at,
|
|
51
|
+
payload: event.payload,
|
|
52
|
+
}
|
|
53
|
+
const digest = crypto
|
|
54
|
+
.createHash('sha256')
|
|
55
|
+
.update(canonicalize(authoritative))
|
|
56
|
+
.digest('hex')
|
|
57
|
+
.slice(0, 32)
|
|
58
|
+
return `event-${digest}`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function boundedLimit(value, hardLimit) {
|
|
62
|
+
return Number.isFinite(value) && value > 0 ? Math.min(Math.floor(value), hardLimit) : hardLimit
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function validateCollaborationEvent(event) {
|
|
66
|
+
const issues = []
|
|
67
|
+
if (!isRecord(event)) return { ok: false, issues: ['event must be an object'] }
|
|
68
|
+
if (event.schema !== ATELIER_COLLABORATION_EVENT_SCHEMA) issues.push('event schema is unsupported')
|
|
69
|
+
if (typeof event.id !== 'string' || !/^event-[a-f0-9]{32}$/.test(event.id)) issues.push('event id is invalid')
|
|
70
|
+
if (typeof event.aggregateId !== 'string' || !event.aggregateId) issues.push('aggregateId is required')
|
|
71
|
+
if (!Number.isInteger(event.version) || event.version < 1) issues.push('version must be a positive integer')
|
|
72
|
+
if (typeof event.type !== 'string' || !/^[a-z][a-z0-9.-]*$/.test(event.type)) issues.push('event type is invalid')
|
|
73
|
+
if (typeof event.actor !== 'string' || !event.actor.trim()) issues.push('actor is required')
|
|
74
|
+
if (!validTimestamp(event.at)) issues.push('event timestamp is invalid')
|
|
75
|
+
if (!isRecord(event.payload)) issues.push('event payload must be an object')
|
|
76
|
+
if (
|
|
77
|
+
typeof event.id === 'string' && /^event-[a-f0-9]{32}$/.test(event.id) &&
|
|
78
|
+
typeof event.aggregateId === 'string' && event.aggregateId &&
|
|
79
|
+
Number.isInteger(event.version) && event.version > 0 &&
|
|
80
|
+
typeof event.type === 'string' && /^[a-z][a-z0-9.-]*$/.test(event.type) &&
|
|
81
|
+
typeof event.actor === 'string' && event.actor.trim() &&
|
|
82
|
+
validTimestamp(event.at) && isRecord(event.payload)
|
|
83
|
+
) {
|
|
84
|
+
try {
|
|
85
|
+
if (event.id !== eventId(event)) issues.push('event id does not match authoritative event content')
|
|
86
|
+
} catch {
|
|
87
|
+
issues.push('authoritative event content cannot be canonicalized')
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return issues.length === 0 ? { ok: true, value: event } : { ok: false, issues }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function createCollaborationEventLedger({
|
|
94
|
+
workspaceRoot = process.cwd(),
|
|
95
|
+
ledgerPath = path.join(workspaceRoot, '.atelier-proposals', 'events.ndjson'),
|
|
96
|
+
clock = () => new Date().toISOString(),
|
|
97
|
+
maxLineBytes = COLLABORATION_LEDGER_LIMITS.maxLineBytes,
|
|
98
|
+
maxBytes = COLLABORATION_LEDGER_LIMITS.maxBytes,
|
|
99
|
+
maxEvents = COLLABORATION_LEDGER_LIMITS.maxEvents,
|
|
100
|
+
} = {}) {
|
|
101
|
+
const root = fs.realpathSync(workspaceRoot)
|
|
102
|
+
const resolvedLedger = path.resolve(ledgerPath)
|
|
103
|
+
const ledgerDirReal = ensureContainedPrivateDirectory({
|
|
104
|
+
workspaceRoot,
|
|
105
|
+
directory: path.dirname(resolvedLedger),
|
|
106
|
+
label: 'collaboration ledger directory',
|
|
107
|
+
})
|
|
108
|
+
const ledgerReal = path.join(ledgerDirReal, path.basename(resolvedLedger))
|
|
109
|
+
const relative = path.relative(root, ledgerReal)
|
|
110
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
111
|
+
throw new Error('collaboration ledger escapes workspace')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const limits = Object.freeze({
|
|
115
|
+
maxLineBytes: boundedLimit(maxLineBytes, COLLABORATION_LEDGER_LIMITS.maxLineBytes),
|
|
116
|
+
maxBytes: boundedLimit(maxBytes, COLLABORATION_LEDGER_LIMITS.maxBytes),
|
|
117
|
+
maxEvents: boundedLimit(maxEvents, COLLABORATION_LEDGER_LIMITS.maxEvents),
|
|
118
|
+
})
|
|
119
|
+
const lockPath = `${ledgerReal}.lock`
|
|
120
|
+
|
|
121
|
+
function failure(status, error, diagnostics = [], stats = {}) {
|
|
122
|
+
return { ok: false, status, error, events: [], diagnostics, stats }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function readAll() {
|
|
126
|
+
const startedAt = performance.now()
|
|
127
|
+
if (!fs.existsSync(ledgerReal)) {
|
|
128
|
+
return { ok: true, status: 200, events: [], diagnostics: [], stats: { bytes: 0, eventCount: 0, durationMs: performance.now() - startedAt } }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let descriptor
|
|
132
|
+
let bytes
|
|
133
|
+
try {
|
|
134
|
+
descriptor = openRegularFileNoFollow(ledgerReal, fs.constants.O_RDONLY)
|
|
135
|
+
bytes = fs.fstatSync(descriptor).size
|
|
136
|
+
} catch (error) {
|
|
137
|
+
return failure(500, `collaboration ledger cannot be inspected: ${error.message}`)
|
|
138
|
+
}
|
|
139
|
+
if (bytes > limits.maxBytes) {
|
|
140
|
+
fs.closeSync(descriptor)
|
|
141
|
+
return failure(413, `collaboration ledger exceeds ${limits.maxBytes} byte hard ceiling`, [{ code: 'ledger-byte-limit', bytes }], { bytes, eventCount: 0 })
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let raw
|
|
145
|
+
try {
|
|
146
|
+
raw = fs.readFileSync(descriptor, 'utf8')
|
|
147
|
+
} catch (error) {
|
|
148
|
+
return failure(500, `collaboration ledger cannot be read: ${error.message}`, [], { bytes, eventCount: 0 })
|
|
149
|
+
} finally {
|
|
150
|
+
fs.closeSync(descriptor)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const diagnostics = []
|
|
154
|
+
const events = []
|
|
155
|
+
const versions = new Map()
|
|
156
|
+
const eventIds = new Set()
|
|
157
|
+
const lines = raw.split('\n')
|
|
158
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
159
|
+
const line = lines[index]
|
|
160
|
+
if (!line) continue
|
|
161
|
+
const lineNumber = index + 1
|
|
162
|
+
const lineBytes = Buffer.byteLength(line)
|
|
163
|
+
if (lineBytes > limits.maxLineBytes) {
|
|
164
|
+
diagnostics.push({ line: lineNumber, code: 'ledger-line-limit', message: `line exceeds ${limits.maxLineBytes} bytes` })
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
167
|
+
if (events.length >= limits.maxEvents) {
|
|
168
|
+
diagnostics.push({ line: lineNumber, code: 'ledger-event-limit', message: `ledger exceeds ${limits.maxEvents} events` })
|
|
169
|
+
break
|
|
170
|
+
}
|
|
171
|
+
let event
|
|
172
|
+
try {
|
|
173
|
+
event = JSON.parse(line)
|
|
174
|
+
} catch (error) {
|
|
175
|
+
diagnostics.push({ line: lineNumber, code: 'ledger-json-invalid', message: error.message })
|
|
176
|
+
continue
|
|
177
|
+
}
|
|
178
|
+
const validation = validateCollaborationEvent(event)
|
|
179
|
+
if (!validation.ok) {
|
|
180
|
+
diagnostics.push({ line: lineNumber, code: 'ledger-event-invalid', message: validation.issues.join('; ') })
|
|
181
|
+
continue
|
|
182
|
+
}
|
|
183
|
+
if (eventIds.has(event.id)) {
|
|
184
|
+
diagnostics.push({ line: lineNumber, code: 'ledger-event-duplicate', message: `duplicate event id ${event.id}` })
|
|
185
|
+
continue
|
|
186
|
+
}
|
|
187
|
+
const previousVersion = versions.get(event.aggregateId) ?? 0
|
|
188
|
+
if (event.version <= previousVersion) {
|
|
189
|
+
diagnostics.push({ line: lineNumber, code: 'ledger-version-order', message: `version ${event.version} does not advance aggregate ${event.aggregateId}` })
|
|
190
|
+
continue
|
|
191
|
+
}
|
|
192
|
+
eventIds.add(event.id)
|
|
193
|
+
versions.set(event.aggregateId, event.version)
|
|
194
|
+
events.push(event)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const stats = { bytes, eventCount: events.length, durationMs: performance.now() - startedAt }
|
|
198
|
+
if (diagnostics.length) {
|
|
199
|
+
return { ok: false, status: 422, error: `collaboration ledger validation failed at ${diagnostics.length} line(s)`, events, diagnostics, stats }
|
|
200
|
+
}
|
|
201
|
+
return { ok: true, status: 200, events, diagnostics: [], stats }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function eventsFor(aggregateId) {
|
|
205
|
+
const id = String(aggregateId || '').trim()
|
|
206
|
+
if (!id) return failure(400, 'aggregateId is required')
|
|
207
|
+
const result = readAll()
|
|
208
|
+
if (!result.ok) return result
|
|
209
|
+
const events = result.events.filter((event) => event.aggregateId === id)
|
|
210
|
+
return {
|
|
211
|
+
...result,
|
|
212
|
+
events,
|
|
213
|
+
currentVersion: events.at(-1)?.version ?? 0,
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function withWriteLock(operation) {
|
|
218
|
+
let descriptor
|
|
219
|
+
try {
|
|
220
|
+
descriptor = fs.openSync(lockPath, 'wx', 0o600)
|
|
221
|
+
fs.writeFileSync(descriptor, `${process.pid}\n`)
|
|
222
|
+
fs.fsyncSync(descriptor)
|
|
223
|
+
} catch (error) {
|
|
224
|
+
if (descriptor != null) fs.closeSync(descriptor)
|
|
225
|
+
if (error?.code === 'EEXIST') return { ok: false, status: 423, error: 'collaboration ledger is locked; retry after the active writer finishes' }
|
|
226
|
+
return { ok: false, status: 500, error: `collaboration ledger lock failed: ${error.message}` }
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
return operation()
|
|
230
|
+
} finally {
|
|
231
|
+
fs.closeSync(descriptor)
|
|
232
|
+
try {
|
|
233
|
+
fs.unlinkSync(lockPath)
|
|
234
|
+
} catch {
|
|
235
|
+
// The completed write remains authoritative; cleanup can be diagnosed
|
|
236
|
+
// by the next writer's explicit locked result.
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function append({ aggregateId, expectedVersion = 0, type, actor, at = clock(), payload = {} }) {
|
|
242
|
+
return withWriteLock(() => {
|
|
243
|
+
const current = eventsFor(aggregateId)
|
|
244
|
+
if (!current.ok) return current
|
|
245
|
+
if (current.currentVersion !== expectedVersion) {
|
|
246
|
+
return {
|
|
247
|
+
ok: false,
|
|
248
|
+
status: 409,
|
|
249
|
+
error: `stale collaboration event refused: expected ${expectedVersion}, current ${current.currentVersion}`,
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (current.stats.eventCount >= limits.maxEvents) {
|
|
253
|
+
return { ok: false, status: 413, error: `collaboration ledger reached ${limits.maxEvents} event hard ceiling; compact it explicitly before appending` }
|
|
254
|
+
}
|
|
255
|
+
const version = current.currentVersion + 1
|
|
256
|
+
const event = {
|
|
257
|
+
schema: ATELIER_COLLABORATION_EVENT_SCHEMA,
|
|
258
|
+
aggregateId,
|
|
259
|
+
version,
|
|
260
|
+
type,
|
|
261
|
+
actor: String(actor || '').trim(),
|
|
262
|
+
at,
|
|
263
|
+
payload,
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
event.id = eventId(event)
|
|
267
|
+
} catch {
|
|
268
|
+
return { ok: false, status: 400, error: 'authoritative event content cannot be canonicalized' }
|
|
269
|
+
}
|
|
270
|
+
const validation = validateCollaborationEvent(event)
|
|
271
|
+
if (!validation.ok) return { ok: false, status: 400, error: validation.issues.join('; ') }
|
|
272
|
+
const line = `${JSON.stringify(event)}\n`
|
|
273
|
+
const lineBytes = Buffer.byteLength(line)
|
|
274
|
+
if (lineBytes > limits.maxLineBytes) {
|
|
275
|
+
return { ok: false, status: 413, error: `collaboration event exceeds ${limits.maxLineBytes} byte line ceiling` }
|
|
276
|
+
}
|
|
277
|
+
if (current.stats.bytes + lineBytes > limits.maxBytes) {
|
|
278
|
+
return { ok: false, status: 413, error: `collaboration ledger would exceed ${limits.maxBytes} byte hard ceiling; compact it explicitly before appending` }
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
secureAppendLine(ledgerReal, line)
|
|
282
|
+
} catch (error) {
|
|
283
|
+
return { ok: false, status: 500, error: `collaboration ledger cannot be appended: ${error.message}` }
|
|
284
|
+
}
|
|
285
|
+
return { ok: true, status: 200, event }
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function materialize(aggregateId, reducer, initial = null) {
|
|
290
|
+
if (typeof reducer !== 'function') return { ok: false, status: 400, error: 'reducer is required', value: initial }
|
|
291
|
+
const result = eventsFor(aggregateId)
|
|
292
|
+
if (!result.ok) return { ...result, value: initial }
|
|
293
|
+
return {
|
|
294
|
+
...result,
|
|
295
|
+
value: result.events.reduce((state, event) => reducer(state, event), initial),
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function compact({
|
|
300
|
+
now = clock(),
|
|
301
|
+
retainPerAggregate = COLLABORATION_LEDGER_LIMITS.retainPerAggregate,
|
|
302
|
+
retainDays = COLLABORATION_LEDGER_LIMITS.retainDays,
|
|
303
|
+
} = {}) {
|
|
304
|
+
return withWriteLock(() => {
|
|
305
|
+
const current = readAll()
|
|
306
|
+
if (!current.ok) return current
|
|
307
|
+
const perAggregate = Math.max(2, boundedLimit(retainPerAggregate, COLLABORATION_LEDGER_LIMITS.retainPerAggregate))
|
|
308
|
+
const days = boundedLimit(retainDays, COLLABORATION_LEDGER_LIMITS.retainDays)
|
|
309
|
+
const cutoff = Date.parse(now) - days * 24 * 60 * 60 * 1000
|
|
310
|
+
if (!Number.isFinite(cutoff)) return { ok: false, status: 400, error: 'compaction timestamp is invalid' }
|
|
311
|
+
|
|
312
|
+
const grouped = new Map()
|
|
313
|
+
for (const event of current.events) {
|
|
314
|
+
const group = grouped.get(event.aggregateId) ?? []
|
|
315
|
+
group.push(event)
|
|
316
|
+
grouped.set(event.aggregateId, group)
|
|
317
|
+
}
|
|
318
|
+
const kept = []
|
|
319
|
+
for (const events of grouped.values()) {
|
|
320
|
+
const first = events[0]
|
|
321
|
+
const latest = events.at(-1)
|
|
322
|
+
const recent = events.filter((event) => Date.parse(event.at) >= cutoff)
|
|
323
|
+
const candidates = [first, ...recent, latest]
|
|
324
|
+
const unique = [...new Map(candidates.map((event) => [event.id, event])).values()]
|
|
325
|
+
.sort((left, right) => left.version - right.version)
|
|
326
|
+
const selected = unique.length <= perAggregate
|
|
327
|
+
? unique
|
|
328
|
+
: [unique[0], ...unique.slice(-(perAggregate - 1))]
|
|
329
|
+
kept.push(...selected)
|
|
330
|
+
}
|
|
331
|
+
// `at` is contributor-controlled metadata and may regress. The physical
|
|
332
|
+
// ledger must retain each aggregate's causal version order after rewrite.
|
|
333
|
+
kept.sort((left, right) => (
|
|
334
|
+
left.aggregateId.localeCompare(right.aggregateId) ||
|
|
335
|
+
left.version - right.version ||
|
|
336
|
+
left.id.localeCompare(right.id)
|
|
337
|
+
))
|
|
338
|
+
const text = kept.length ? `${kept.map((event) => JSON.stringify(event)).join('\n')}\n` : ''
|
|
339
|
+
if (Buffer.byteLength(text) > limits.maxBytes) {
|
|
340
|
+
return { ok: false, status: 413, error: 'compacted collaboration ledger still exceeds the byte hard ceiling' }
|
|
341
|
+
}
|
|
342
|
+
atomicReplacePrivateText(ledgerReal, text)
|
|
343
|
+
return {
|
|
344
|
+
ok: true,
|
|
345
|
+
status: 200,
|
|
346
|
+
before: current.events.length,
|
|
347
|
+
after: kept.length,
|
|
348
|
+
removed: current.events.length - kept.length,
|
|
349
|
+
retainPerAggregate: perAggregate,
|
|
350
|
+
retainDays: days,
|
|
351
|
+
}
|
|
352
|
+
})
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return Object.freeze({
|
|
356
|
+
ledgerPath: ledgerReal,
|
|
357
|
+
lockPath,
|
|
358
|
+
limits,
|
|
359
|
+
readAll,
|
|
360
|
+
eventsFor,
|
|
361
|
+
append,
|
|
362
|
+
materialize,
|
|
363
|
+
compact,
|
|
364
|
+
})
|
|
365
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ATELIER_COLLABORATION_EVENT_SCHEMA,
|
|
3
|
+
createCollaborationEventLedger,
|
|
4
|
+
validateCollaborationEvent,
|
|
5
|
+
} from './event-ledger.mjs'
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
ATELIER_PROPOSAL_SCHEMA,
|
|
9
|
+
ATELIER_PROPOSALS_SCHEMA,
|
|
10
|
+
COPY_ONLY_PROPOSAL_CAPABILITY,
|
|
11
|
+
PROPOSAL_REVIEW_STATUSES,
|
|
12
|
+
acceptedProposalCopy,
|
|
13
|
+
canTransitionProposal,
|
|
14
|
+
copyOnlyActionSummary,
|
|
15
|
+
createProposalStore,
|
|
16
|
+
validateCopyOnlyProposalAuthority,
|
|
17
|
+
} from './proposals.mjs'
|