gabriel / musehub public
genesis.py python
618 lines 22.5 KB
Raw
sha256:bba4b69de173366aeb7d490d687ccffb6db9726374033addece89cf2b87642d8 docs: add production launch discovery report and infra laun… Sonnet 5 7 days ago
1 """Genesis ID computation — canonical sha256: identities for every first-class entity.
2
3 Every first-class semantic entity in the Muse ecosystem has an identity derived
4 from the minimal immutable facts about the moment it was declared to exist: its
5 **genesis context**. The formula is universal:
6
7 entity_id = blob_id(NUL.join(genesis_fields).encode())
8
9 This is a **verifiability guarantee**: anyone who knows the genesis context of
10 an entity can independently derive its canonical identity and verify it matches.
11 UUIDs carry no such guarantee — they are opaque, random, and unverifiable.
12
13 Separator
14 ---------
15 ``_SEP = "\\x00"`` (NUL byte) — the same separator used by
16 ``muse.core.snapshot`` for snapshot and commit IDs. It prevents
17 separator-injection attacks from field values containing ``|``, ``:``, or
18 other common separators.
19
20 CONTRACT
21 --------
22 Functions that have counterparts in ``muse.core.genesis`` (CLI-side) MUST
23 produce identical output for the same inputs. Any change to the hashing
24 algorithm or field ordering must be applied to both sides simultaneously.
25 A mismatch is a silent identity bug.
26
27 Cross-verified by ``tests/test_genesis_ids.py``.
28 """
29
30 from muse.core.types import blob_id, public_key_fingerprint, short_id
31
32 # Must match muse.core.snapshot._SEP and muse.core.genesis._SEP exactly.
33 _SEP = "\x00"
34
35 def _genesis_hash(*fields: str) -> str:
36 """Return ``sha256:<hex>`` of NUL-joined *fields*.
37
38 All fields must be non-empty strings representing stable, immutable
39 facts about the entity's declaration event. Field order is significant
40 — callers are responsible for a consistent, documented order.
41 """
42 payload = _SEP.join(fields).encode()
43 return blob_id(payload)
44
45 # ---------------------------------------------------------------------------
46 # Identity
47 # ---------------------------------------------------------------------------
48
49 def compute_derived_agent_id(handle: str) -> str:
50 """Return a deterministic genesis identity_id for an agent known only by handle.
51
52 Used when an agent appears in commit metadata but was never formally registered
53 with a key pair. The genesis context is the type discriminator + handle:
54
55 identity_id = sha256("agent_handle" NUL handle)
56
57 Anyone who knows the agent's handle can independently verify this ID.
58 """
59 return _genesis_hash("agent_handle", handle)
60
61 def compute_derived_org_id(handle: str) -> str:
62 """Return a deterministic genesis identity_id for an org known only by handle.
63
64 identity_id = sha256("org_handle" NUL handle)
65
66 Anyone who knows the org's handle can independently verify this ID.
67 """
68 return _genesis_hash("org_handle", handle)
69
70 def compute_identity_id(public_key_bytes: bytes) -> str:
71 """Return the canonical identity ID for an Ed25519 public key.
72
73 The identity IS the key pair — the public key bytes are the complete,
74 immutable genesis context. No other fields are needed or included.
75
76 This is the server-side equivalent of the MSign fingerprint: anyone who
77 holds the public key can independently derive the identity_id.
78
79 identity_id = public_key_fingerprint(public_key_bytes)
80 """
81 return public_key_fingerprint(public_key_bytes)
82
83 # ---------------------------------------------------------------------------
84 # Repo
85 # ---------------------------------------------------------------------------
86
87 def compute_repo_id(
88 owner_identity_id: str,
89 slug: str,
90 domain: str,
91 created_at_iso: str,
92 ) -> str:
93 """Return the canonical repo ID from its declaration event.
94
95 The repo declaration is immutable: owner, slug, domain, and timestamp
96 uniquely identify the moment a repo was brought into existence.
97 Subsequent renames or transfers update ``owner``/``slug`` alias columns
98 only — the ``repo_id`` never changes.
99
100 repo_id = sha256(owner_identity_id NUL slug NUL domain NUL created_at_iso)
101
102 *owner_identity_id* is itself a genesis-addressed identity ID
103 (``sha256:<pubkey_hash>``), so the repo ID transitively encodes the
104 owner's cryptographic identity.
105 """
106 return _genesis_hash(owner_identity_id, slug, domain, created_at_iso)
107
108 # ---------------------------------------------------------------------------
109 # Issue
110 # ---------------------------------------------------------------------------
111
112 def compute_issue_id(
113 repo_id: str,
114 author_identity_id: str,
115 created_at_iso: str,
116 ) -> str:
117 """Return the canonical issue ID from its declaration event.
118
119 The integer ``number`` field is a human-readable alias; the ``issue_id``
120 is the permanent, verifiable identity.
121
122 issue_id = sha256(repo_id NUL author_identity_id NUL created_at_iso)
123 """
124 return _genesis_hash(repo_id, author_identity_id, created_at_iso)
125
126 # ---------------------------------------------------------------------------
127 # Proposal
128 # ---------------------------------------------------------------------------
129
130 def compute_proposal_id(
131 repo_id: str,
132 author_identity_id: str,
133 from_branch: str,
134 to_branch: str,
135 created_at_iso: str,
136 ) -> str:
137 """Return the canonical proposal ID from its declaration event.
138
139 The proposal declares intent to merge *from_branch* into *to_branch* at
140 a specific moment. ``proposal_number`` is a human-readable alias.
141
142 proposal_id = sha256(repo_id NUL author_identity_id NUL from_branch NUL to_branch NUL created_at_iso)
143 """
144 return _genesis_hash(repo_id, author_identity_id, from_branch, to_branch, created_at_iso)
145
146 # ---------------------------------------------------------------------------
147 # Release
148 # ---------------------------------------------------------------------------
149
150 def compute_release_id(
151 repo_id: str,
152 tag: str,
153 created_at_iso: str,
154 ) -> str:
155 """Return the canonical release ID from its declaration event.
156
157 The semver tag string (e.g. ``v1.2.3``) is stable per repo. Combined
158 with ``repo_id`` and ``created_at`` it uniquely identifies the release
159 declaration.
160
161 release_id = sha256(repo_id NUL tag NUL created_at_iso)
162
163 CONTRACT: must match ``muse.core.genesis.compute_release_id`` exactly.
164 """
165 return _genesis_hash(repo_id, tag, created_at_iso)
166
167 # ---------------------------------------------------------------------------
168 # Tag (wire tag / semantic label)
169 # ---------------------------------------------------------------------------
170
171 def compute_tag_id(
172 repo_id: str,
173 commit_id: str,
174 label: str,
175 created_at_iso: str,
176 ) -> str:
177 """Return the canonical tag ID from its declaration event.
178
179 A semantic label (e.g. ``emotion:joyful``, ``section:verse``) is applied
180 to a specific commit at a specific time. The same label can be applied
181 to different commits, so ``commit_id`` and ``created_at`` are both part
182 of the genesis context.
183
184 tag_id = sha256(repo_id NUL commit_id NUL label NUL created_at_iso)
185
186 CONTRACT: must match ``muse.core.genesis.compute_tag_id`` exactly.
187 """
188 return _genesis_hash(repo_id, commit_id, label, created_at_iso)
189
190 # ---------------------------------------------------------------------------
191 # Branch
192 # ---------------------------------------------------------------------------
193
194 def compute_branch_id(repo_id: str, name: str) -> str:
195 """Return the canonical branch ID from its declaration context.
196
197 A branch is uniquely identified by its name within a repo. The same
198 name always refers to the same logical branch — delete + recreate yields
199 the same ID because it is still "branch *name* in repo *repo_id*".
200
201 branch_id = sha256(repo_id NUL name)
202 """
203 return _genesis_hash(repo_id, name)
204
205 # ---------------------------------------------------------------------------
206 # Session
207 # ---------------------------------------------------------------------------
208
209 def compute_session_id(
210 repo_id: str,
211 author_identity_id: str,
212 started_at_iso: str,
213 ) -> str:
214 """Return the canonical session ID from its declaration event.
215
216 A recording session is declared at a specific moment by a specific
217 identity within a specific repo.
218
219 session_id = sha256(repo_id NUL author_identity_id NUL started_at_iso)
220 """
221 return _genesis_hash(repo_id, author_identity_id, started_at_iso)
222
223 # ---------------------------------------------------------------------------
224 # Mist
225 # ---------------------------------------------------------------------------
226
227 def compute_mist_id(content: bytes) -> str:
228 """Return the canonical mist ID — content-addressed, full sha256: form.
229
230 Mists are immutable single-artifact shares: the content IS the identity.
231 The previous 12-char base-58 encoding was a display alias; this function
232 returns the full canonical ID. The short display form is derived
233 separately from the first 12 hex chars of the digest.
234
235 mist_id = blob_id(content)
236 """
237 return blob_id(content)
238
239 def mist_short_id(mist_id: str) -> str:
240 """Return the 12-char human-readable alias for a mist.
241
242 Derived from the first 12 hex chars of the sha256 digest (after the
243 ``sha256:`` prefix). Used for short URLs and display; not a PK.
244 """
245 return short_id(mist_id, strip=True)
246
247 # ---------------------------------------------------------------------------
248 # Issue comment
249 # ---------------------------------------------------------------------------
250
251 def compute_comment_id(
252 parent_id: str,
253 author_identity_id: str,
254 created_at_iso: str,
255 ) -> str:
256 """Return the canonical comment ID from its declaration event.
257
258 Used for both issue comments and proposal inline comments.
259 *parent_id* is the ``issue_id`` or ``proposal_id`` the comment belongs to.
260
261 comment_id = sha256(parent_id NUL author_identity_id NUL created_at_iso)
262 """
263 return _genesis_hash(parent_id, author_identity_id, created_at_iso)
264
265 # ---------------------------------------------------------------------------
266 # Proposal review
267 # ---------------------------------------------------------------------------
268
269 def compute_review_id(
270 proposal_id: str,
271 reviewer_identity_id: str,
272 created_at_iso: str,
273 ) -> str:
274 """Return the canonical review ID from its declaration event.
275
276 A formal review submission is declared by a specific reviewer on a
277 specific proposal at a specific time.
278
279 review_id = sha256(proposal_id NUL reviewer_identity_id NUL created_at_iso)
280 """
281 return _genesis_hash(proposal_id, reviewer_identity_id, created_at_iso)
282
283 # ---------------------------------------------------------------------------
284 # Label
285 # ---------------------------------------------------------------------------
286
287 def compute_label_id(
288 repo_id: str,
289 name: str,
290 created_at_iso: str,
291 ) -> str:
292 """Return the canonical label ID from its declaration event.
293
294 A label is scoped to a repo and declared at a specific moment. The name
295 is immutable after creation — updates change the colour/description only,
296 never the identity.
297
298 label_id = sha256(repo_id NUL name NUL created_at_iso)
299 """
300 return _genesis_hash(repo_id, name, created_at_iso)
301
302 # ---------------------------------------------------------------------------
303 # Release asset
304 # ---------------------------------------------------------------------------
305
306 def compute_asset_id(
307 release_id: str,
308 filename: str,
309 created_at_iso: str,
310 ) -> str:
311 """Return the canonical asset ID from its declaration event.
312
313 A release asset is a named artifact attached to a specific release at a
314 specific moment. The filename is unique per release — adding the timestamp
315 makes the formula robust to edge cases (e.g. delete + re-upload).
316
317 asset_id = sha256(release_id NUL filename NUL created_at_iso)
318 """
319 return _genesis_hash(release_id, filename, created_at_iso)
320
321 # ---------------------------------------------------------------------------
322 # Webhook
323 # ---------------------------------------------------------------------------
324
325 def compute_webhook_id(
326 repo_id: str,
327 url: str,
328 created_at_iso: str,
329 ) -> str:
330 """Return the canonical webhook ID from its registration event.
331
332 A webhook subscription is declared by registering a URL to receive events
333 from a specific repo at a specific moment. The declaration event is
334 immutable — the URL, repo, and timestamp together uniquely identify it.
335
336 webhook_id = sha256(repo_id NUL url NUL created_at_iso)
337 """
338 return _genesis_hash(repo_id, url, created_at_iso)
339
340 # ---------------------------------------------------------------------------
341 # Fork
342 # ---------------------------------------------------------------------------
343
344 def compute_fork_id(
345 source_repo_id: str,
346 fork_repo_id: str,
347 created_at_iso: str,
348 ) -> str:
349 """Return the canonical fork relationship ID from its declaration event.
350
351 A fork relationship is immutable: a specific repo (fork_repo_id) was forked
352 from a specific source (source_repo_id) at a specific moment.
353
354 fork_id = sha256(source_repo_id NUL fork_repo_id NUL created_at_iso)
355 """
356 return _genesis_hash(source_repo_id, fork_repo_id, created_at_iso)
357
358 # ---------------------------------------------------------------------------
359 # Collaborator
360 # ---------------------------------------------------------------------------
361
362 def compute_collaborator_id(
363 repo_id: str,
364 identity_id: str,
365 created_at_iso: str,
366 ) -> str:
367 """Return the canonical collaborator ID from its declaration event.
368
369 A collaborator invitation is a first-class declaration: a specific identity
370 is granted access to a specific repo at a specific moment.
371
372 collaborator_id = sha256(repo_id NUL identity_id NUL created_at_iso)
373 """
374 return _genesis_hash(repo_id, identity_id, created_at_iso)
375
376 # ---------------------------------------------------------------------------
377 # Auth key
378 # ---------------------------------------------------------------------------
379
380 def compute_key_id(
381 identity_id: str,
382 public_key_b64: str,
383 ) -> str:
384 """Return the canonical auth key ID from its registration context.
385
386 The key IS its bytes — there is no stronger genesis context. No timestamp
387 is needed because a given public key can only be registered once per
388 identity (enforced by the UNIQUE fingerprint constraint).
389
390 key_id = sha256(identity_id NUL public_key_b64)
391 """
392 return _genesis_hash(identity_id, public_key_b64)
393
394 # ---------------------------------------------------------------------------
395 # Domain plugin
396 # ---------------------------------------------------------------------------
397
398 def compute_domain_id(
399 author_slug: str,
400 slug: str,
401 created_at_iso: str,
402 ) -> str:
403 """Return the canonical domain plugin ID from its registration event.
404
405 A domain plugin is identified by its scoped name (@author_slug/slug) and
406 the moment it was registered. The scoped name is already enforced as a
407 unique composite in the DB, so the timestamp distinguishes re-registration
408 after deletion.
409
410 domain_id = sha256(author_slug NUL slug NUL created_at_iso)
411 """
412 return _genesis_hash(author_slug, slug, created_at_iso)
413
414 # ---------------------------------------------------------------------------
415 # Intel result
416 # ---------------------------------------------------------------------------
417
418 def compute_intel_result_id(
419 repo_id: str,
420 intel_type: str,
421 ref: str,
422 ) -> str:
423 """Return the canonical intel result ID from its computation context.
424
425 An intelligence result is uniquely identified by what was computed
426 (intel_type), for which repo, and at which commit (ref).
427
428 result_id = sha256(repo_id NUL intel_type NUL ref)
429 """
430 return _genesis_hash(repo_id, intel_type, ref)
431
432 # ---------------------------------------------------------------------------
433 # Attestation
434 # ---------------------------------------------------------------------------
435
436 def compute_attestation_id(
437 attester: str,
438 subject: str,
439 claim: str,
440 issued_at_iso: str,
441 ) -> str:
442 """Return the canonical attestation ID.
443
444 attestation_id = sha256(attester NUL subject NUL claim NUL issued_at_iso)
445
446 Attestations are issued once and never mutated; ``issued_at_iso`` breaks
447 ties so the same claim can be re-issued after a previous one is revoked.
448 """
449 return _genesis_hash(attester, subject, claim, issued_at_iso)
450
451 # ---------------------------------------------------------------------------
452 # Bridge mirror
453 # ---------------------------------------------------------------------------
454
455 def compute_bridge_mirror_id(repo_id: str, git_remote_url: str) -> str:
456 """Return the canonical bridge mirror ID.
457
458 A Git mirror registration is uniquely identified by the repo it mirrors
459 and the remote URL — the UNIQUE(repo_id, git_remote_url) constraint
460 enforces exactly one row, so no timestamp is needed.
461
462 bridge_mirror_id = sha256(repo_id NUL git_remote_url)
463 """
464 return _genesis_hash(repo_id, git_remote_url)
465
466 # ---------------------------------------------------------------------------
467 # Domain install
468 # ---------------------------------------------------------------------------
469
470 def compute_domain_install_id(user_id: str, domain_id: str) -> str:
471 """Return the canonical domain install ID.
472
473 A domain installation is uniquely identified by the user and the domain —
474 the UNIQUE(user_id, domain_id) constraint enforces exactly one row per
475 user per domain, so no timestamp is needed.
476
477 domain_install_id = sha256(user_id NUL domain_id)
478 """
479 return _genesis_hash(user_id, domain_id)
480
481 # ---------------------------------------------------------------------------
482 # Issue event
483 # ---------------------------------------------------------------------------
484
485 def compute_issue_event_id(
486 issue_id: str,
487 event_type: str,
488 actor: str,
489 created_at_iso: str,
490 ) -> str:
491 """Return the canonical issue event ID.
492
493 An activity event on an issue is uniquely identified by the issue it
494 belongs to, the type of event, the actor who triggered it, and the
495 moment it occurred.
496
497 issue_event_id = sha256(issue_id NUL event_type NUL actor NUL created_at_iso)
498 """
499 return _genesis_hash(issue_id, event_type, actor, created_at_iso)
500
501 # ---------------------------------------------------------------------------
502 # Background job
503 # ---------------------------------------------------------------------------
504
505 def compute_job_id(repo_id: str, job_type: str, created_at_iso: str) -> str:
506 """Return the canonical background job ID.
507
508 A background job is uniquely identified by the repo it operates on,
509 the type of work to perform, and the moment it was enqueued. If the
510 same job_type is enqueued twice for the same repo at the same instant,
511 the duplicate is rejected — which is the correct deduplication behavior.
512
513 job_id = sha256(repo_id NUL job_type NUL created_at_iso)
514 """
515 return _genesis_hash(repo_id, job_type, created_at_iso)
516
517 # ---------------------------------------------------------------------------
518 # Webhook delivery
519 # ---------------------------------------------------------------------------
520
521 def compute_webhook_delivery_id(
522 webhook_id: str,
523 event_type: str,
524 attempt: int,
525 delivered_at_iso: str,
526 ) -> str:
527 """Return the canonical webhook delivery ID.
528
529 Each delivery attempt is a distinct event: the same webhook can fire
530 multiple times for the same event_type (retries increment ``attempt``).
531 Together they form a unique declaration.
532
533 delivery_id = sha256(webhook_id NUL event_type NUL str(attempt) NUL delivered_at_iso)
534 """
535 return _genesis_hash(webhook_id, event_type, str(attempt), delivered_at_iso)
536
537 # ---------------------------------------------------------------------------
538 # Coord task
539 # ---------------------------------------------------------------------------
540
541 def compute_task_id(
542 repo_id: str,
543 queue: str,
544 created_by: str,
545 created_at_iso: str,
546 ) -> str:
547 """Return the canonical coord task ID from its enqueue event.
548
549 A task is declared by an agent into a queue within a repo at a specific
550 moment. Same inputs always produce the same ID — enabling idempotent
551 enqueue (re-enqueueing the same task at the same instant is a no-op).
552
553 task_id = sha256(repo_id NUL queue NUL created_by NUL created_at_iso)
554 """
555 return _genesis_hash(repo_id, queue, created_by, created_at_iso)
556
557 # ---------------------------------------------------------------------------
558 # Coord reservation
559 # ---------------------------------------------------------------------------
560
561 def compute_reservation_id(
562 repo_id: str,
563 agent_id: str,
564 addresses_sorted_joined: str,
565 created_at_iso: str,
566 ) -> str:
567 """Return the canonical coord reservation ID from its declaration event.
568
569 A symbol reservation is declared by a specific agent for a specific set
570 of addresses in a specific repo at a specific moment.
571
572 reservation_id = sha256(repo_id NUL agent_id NUL addresses_sorted_joined NUL created_at_iso)
573
574 *addresses_sorted_joined* must be ``",".join(sorted(addresses))`` from the
575 caller — the comma is the intra-field separator, distinct from the NUL
576 inter-field separator used by ``_genesis_hash``.
577 """
578 return _genesis_hash(repo_id, agent_id, addresses_sorted_joined, created_at_iso)
579
580 # ---------------------------------------------------------------------------
581 # Proposal simulation
582 # ---------------------------------------------------------------------------
583
584 def compute_simulation_id(
585 proposal_id: str,
586 simulation_type: str,
587 from_branch_commit_id: str,
588 ) -> str:
589 """Return the canonical simulation ID from its computation context.
590
591 A simulation result is uniquely identified by the proposal it was run for,
592 the type of simulation, and the from_branch commit tip at the time of the
593 run. Re-running the same simulation after the branch advances yields a
594 different ID — the old result remains queryable for diffing.
595
596 simulation_id = sha256(proposal_id NUL simulation_type NUL from_branch_commit_id)
597 """
598 return _genesis_hash(proposal_id, simulation_type, from_branch_commit_id)
599
600 # ---------------------------------------------------------------------------
601 # MPay claim
602 # ---------------------------------------------------------------------------
603
604 def compute_mpay_claim_id(
605 sender: str,
606 recipient: str,
607 amount_nano: int,
608 nonce_hex: str,
609 ) -> str:
610 """Return the canonical MPay claim ID.
611
612 claim_id = sha256(sender NUL recipient NUL str(amount_nano) NUL nonce_hex)
613
614 ``nonce_hex`` is the 32-byte random hex nonce embedded in the MSign payment
615 payload; it makes each claim globally unique even for repeated same-amount
616 transfers between the same pair.
617 """
618 return _genesis_hash(sender, recipient, str(amount_nano), nonce_hex)
File History 2 commits
sha256:bba4b69de173366aeb7d490d687ccffb6db9726374033addece89cf2b87642d8 docs: add production launch discovery report and infra laun… Sonnet 5 7 days ago
sha256:eb0928124669c933c0ef852bea1ad0c56649cf21bd7e9663c3b51004771b0591 docs: add MuseHub cloud identity/AWS operating model and Go… Sonnet 5 patch 8 days ago