pgc-assembler 2.0.0__py3-none-any.whl

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.
assembler/indexes.py ADDED
@@ -0,0 +1,421 @@
1
+ """
2
+ indexes.py — cross-domain query indexes over the assembled snapshot.
3
+
4
+ Three inspection indexes, built AFTER composition (the assembler is the only point with the full
5
+ federated view of all domains — the PGC successor to RI-0's cross-structure `build` aggregation):
6
+
7
+ * artifact_index/index.json — FQDN → domain / kind / owner_subdomain / canonical_path /
8
+ evidence_paths / per-domain addresses. Consumed by `si`.
9
+ * kind_index/index.json — rich by-kind cross-reference (workflows / CCs / CTs / CSs / intents /
10
+ runtime_bindings / actors / events + cross-refs + vocabulary +
11
+ domain groupings). The si/tooling query database.
12
+ * store_index/index.json — store → owning storage STRUCTURE, declared path, and binding
13
+ surface (RB + CS + workflows + consumer CCs).
14
+
15
+ The assembler produces an INSPECTABLE snapshot, not merely an executable one: the compiler owns
16
+ the correctness of one domain, the assembler the correctness and indexing of the composition, and
17
+ `snapshot_inspector` the read-only query interface over it. An index is a composition-level fact —
18
+ no single domain build can compute one — which is why all three live here and none in the compiler.
19
+
20
+ Re-emission only: every fact is read from materialized projections in the consolidated snapshot
21
+ (canonical/<domain>, vocabulary/<domain>, evidence/<domain>). Zero re-derivation, deterministic
22
+ (sorted keys, no timestamps), fail-hard on malformed input.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import json
28
+ from pathlib import Path
29
+ from typing import Any
30
+
31
+ SCHEMA_VERSION = "v0"
32
+
33
+
34
+ # ---------------------------------------------------------------------------
35
+ # Shared readers over the consolidated snapshot
36
+ # ---------------------------------------------------------------------------
37
+
38
+ def _load_canonical(out_root: Path) -> dict[str, dict]:
39
+ """fqdn → canonical artifact doc, across all domains (canonical/<domain>/<type>/*.json)."""
40
+ canon = out_root / "canonical"
41
+ docs: dict[str, dict] = {}
42
+ if not canon.is_dir():
43
+ return docs
44
+ for f in sorted(canon.rglob("*.json")):
45
+ if f.name == "metadata.json":
46
+ continue
47
+ raw = json.loads(f.read_text(encoding="utf-8"))
48
+ fqdn = raw.get("fqdn_id")
49
+ if fqdn and "::" in fqdn:
50
+ raw["_canonical_path"] = f.relative_to(out_root).as_posix()
51
+ docs[fqdn] = raw
52
+ return docs
53
+
54
+
55
+ def _load_membership(out_root: Path) -> dict[str, dict[str, str]]:
56
+ """fqdn → {domain: hex_address}, from each vocabulary/<domain>/reverse.json."""
57
+ vocab = out_root / "vocabulary"
58
+ membership: dict[str, dict[str, str]] = {}
59
+ if not vocab.is_dir():
60
+ return membership
61
+ for d in sorted(p for p in vocab.iterdir() if p.is_dir()):
62
+ rev_path = d / "reverse.json"
63
+ if not rev_path.is_file():
64
+ continue
65
+ for fqdn, addr in json.loads(rev_path.read_text(encoding="utf-8")).items():
66
+ membership.setdefault(fqdn, {})[d.name] = addr
67
+ return membership
68
+
69
+
70
+ def _load_evidence_edges(out_root: Path) -> list[dict]:
71
+ """All semantic edges from every evidence/<domain>/evidence.json.
72
+
73
+ `evidence.json` carries the SEMANTIC graph (WF_BINDS_RB, WF_CONTAINS_NODE, CC_BINDS_CS,
74
+ NODE_NEXT, …), keyed by FQDN. Its sibling `evidence_graph.json` is the COMPILE TRACE
75
+ (STAGE_SEQUENCE / CAUSALITY, keyed by event id) and holds none of those kinds — reading it
76
+ here yielded zero matches and left every consumer's cross-reference silently empty.
77
+ """
78
+ evidence = out_root / "evidence"
79
+ edges: list[dict] = []
80
+ if not evidence.is_dir():
81
+ return edges
82
+ for eg in sorted(evidence.glob("*/evidence.json")):
83
+ data = json.loads(eg.read_text(encoding="utf-8"))
84
+ edges.extend(data.get("edges", []))
85
+ return edges
86
+
87
+
88
+ def build_artifact_index(out_root: Path) -> dict[str, Any]:
89
+ docs = _load_canonical(out_root)
90
+ membership = _load_membership(out_root)
91
+ evidence_root = out_root / "evidence"
92
+
93
+ artifacts: dict[str, dict] = {}
94
+ for fqdn, raw in docs.items():
95
+ domain = fqdn.split("::", 1)[0]
96
+ scopes = membership.get(fqdn, {})
97
+ evidence_paths = {}
98
+ for scope in sorted(scopes):
99
+ eg_rel = f"evidence/{scope}/evidence_graph.json"
100
+ if (evidence_root / scope / "evidence_graph.json").is_file():
101
+ evidence_paths[scope] = eg_rel
102
+ artifacts[fqdn] = {
103
+ "domain": domain,
104
+ "kind": raw.get("artifact_type"),
105
+ "owner_subdomain": (raw.get("frontmatter") or {}).get("concern") or None,
106
+ "canonical_path": raw["_canonical_path"],
107
+ "evidence_paths": evidence_paths,
108
+ "addresses": {s: scopes[s] for s in sorted(scopes)},
109
+ }
110
+ return {
111
+ "schema_version": SCHEMA_VERSION,
112
+ "generated_by": "snapshot_assembler",
113
+ "artifact_count": len(artifacts),
114
+ "artifacts": dict(sorted(artifacts.items())),
115
+ }
116
+
117
+
118
+ # ---------------------------------------------------------------------------
119
+ # kind_index (si query database)
120
+ # ---------------------------------------------------------------------------
121
+
122
+ def build_kind_index(out_root: Path) -> dict[str, Any]:
123
+ docs = _load_canonical(out_root)
124
+ edges = _load_evidence_edges(out_root)
125
+
126
+ workflows: dict[str, dict] = {}
127
+ capability_contracts: dict[str, dict] = {}
128
+ capability_transforms: dict[str, dict] = {}
129
+ capability_side_effects: dict[str, dict] = {}
130
+ intents: dict[str, dict] = {}
131
+ runtime_bindings: dict[str, dict] = {}
132
+ actors: dict[str, dict] = {}
133
+ events: dict[str, dict] = {}
134
+
135
+ for fqdn, doc in docs.items():
136
+ atype = doc.get("artifact_type", "")
137
+ ns = doc.get("namespace", fqdn.split("::")[0])
138
+ fm = doc.get("frontmatter", {})
139
+ core = fm.get("core", {})
140
+ base = {
141
+ "fqdn": fqdn, "namespace": ns, "code": fqdn.split("::")[-1],
142
+ "version": fm.get("version", "v0"), "raw": doc,
143
+ }
144
+ if atype == "WF":
145
+ workflows[fqdn] = {**base, "subdomain": fm.get("subdomain", ""),
146
+ "summary": core.get("summary", ""), "start_node": core.get("start_node", ""),
147
+ "nodes": core.get("nodes", {}), "actor_context": core.get("actor_context", "")}
148
+ elif atype == "CC":
149
+ rsc = core.get("result_status_contract", {})
150
+ capability_contracts[fqdn] = {**base, "summary": core.get("summary", ""),
151
+ "outcomes": rsc.get("allowed", []), "pipeline": core.get("pipeline", []),
152
+ "inputs": core.get("inputs", {}), "outputs": core.get("outputs", {})}
153
+ elif atype == "CT":
154
+ machine = fm.get("machine", {})
155
+ capability_transforms[fqdn] = {**base, "summary": core.get("summary", fm.get("description", "")),
156
+ "purity": machine.get("ct_purity", "ct_pure"),
157
+ "inputs": core.get("inputs", {}), "outputs": core.get("outputs", {})}
158
+ elif atype == "CS":
159
+ capability_side_effects[fqdn] = {**base, "operations": core.get("operations", {})}
160
+ elif atype == "IN":
161
+ intents[fqdn] = base
162
+ elif atype == "RB":
163
+ runtime_bindings[fqdn] = {**base, "bindings": core.get("bindings", {})}
164
+ elif atype == "AC":
165
+ actors[fqdn] = {**base, "type": core.get("type", ""), "attributes": core.get("attributes", {})}
166
+ elif atype == "EV":
167
+ events[fqdn] = {**base, "schema": core.get("schema", {})}
168
+
169
+ # --- cross-references ---
170
+ wf_to_ccs = {
171
+ wf: [n.get("fqdn_id", f"{w['namespace']}::{code}")
172
+ for code, n in w["nodes"].items() if n.get("type") == "CC"]
173
+ for wf, w in workflows.items()
174
+ }
175
+ cc_outcomes = {cc: c["outcomes"] for cc, c in capability_contracts.items()}
176
+ cc_to_ct_cs: dict[str, list[str]] = {}
177
+ for cc, c in capability_contracts.items():
178
+ cc_to_ct_cs[cc] = [s["transform"] for s in c["pipeline"] if "transform" in s]
179
+ cc_upstream: dict[str, list[str]] = {}
180
+ cc_downstream: dict[str, list[str]] = {}
181
+ for edge in edges:
182
+ k, src, tgt = edge.get("kind", ""), edge.get("source_fqdn", ""), edge.get("target_fqdn", "")
183
+ if k == "NODE_NEXT" and src and tgt:
184
+ cc_downstream.setdefault(src, [])
185
+ if tgt not in cc_downstream[src]:
186
+ cc_downstream[src].append(tgt)
187
+ cc_upstream.setdefault(tgt, [])
188
+ if src not in cc_upstream[tgt]:
189
+ cc_upstream[tgt].append(src)
190
+
191
+ # --- domain / subdomain groupings ---
192
+ domains: dict[str, list[str]] = {}
193
+ subdomains: dict[str, list[str]] = {}
194
+ for fqdn in docs:
195
+ domains.setdefault(fqdn.split("::")[0], []).append(fqdn)
196
+ for wf, w in workflows.items():
197
+ if w["subdomain"]:
198
+ subdomains.setdefault(w["subdomain"], []).append(wf)
199
+
200
+ return {
201
+ "schema_version": SCHEMA_VERSION,
202
+ "generated_by": "snapshot_assembler",
203
+ "workflows": dict(sorted(workflows.items())),
204
+ "capability_contracts": dict(sorted(capability_contracts.items())),
205
+ "capability_transforms": dict(sorted(capability_transforms.items())),
206
+ "capability_side_effects": dict(sorted(capability_side_effects.items())),
207
+ "intents": dict(sorted(intents.items())),
208
+ "runtime_bindings": dict(sorted(runtime_bindings.items())),
209
+ "actors": dict(sorted(actors.items())),
210
+ "events": dict(sorted(events.items())),
211
+ "cross_references": {
212
+ "wf_to_ccs": dict(sorted(wf_to_ccs.items())),
213
+ "cc_to_ct_cs": dict(sorted(cc_to_ct_cs.items())),
214
+ "cc_outcomes": dict(sorted(cc_outcomes.items())),
215
+ "cc_upstream": dict(sorted(cc_upstream.items())),
216
+ "cc_downstream": dict(sorted(cc_downstream.items())),
217
+ },
218
+ "domains": {d: sorted(v) for d, v in sorted(domains.items())},
219
+ "subdomains": {s: sorted(v) for s, v in sorted(subdomains.items())},
220
+ }
221
+
222
+
223
+ # ---------------------------------------------------------------------------
224
+ # store_index (storage ownership + binding surface)
225
+ # ---------------------------------------------------------------------------
226
+
227
+ _DATA_ROOT_TEMPLATE = "{{module_data_root}}/"
228
+
229
+
230
+ def build_store_index(out_root: Path) -> dict[str, Any]:
231
+ """Materialize the store-ownership join the composed snapshot already declares in three places:
232
+
233
+ storage STRUCTURE artifacts → core.entity_stores (store name → data path)
234
+ RB artifacts → core.bindings (CS → policy path)
235
+ evidence.json → WF_BINDS_RB, WF_CONTAINS_NODE, CC_BINDS_CS
236
+
237
+ into: store → owning structure, declared path, binding surface (RB + CS + workflows +
238
+ consumer CCs). Re-emission of declared facts only; deterministic; no policing.
239
+ """
240
+ docs = _load_canonical(out_root)
241
+ stores = _declared_stores(docs)
242
+ rb_paths = _rb_store_paths(docs)
243
+ cc_stores = _cc_declared_stores(docs)
244
+
245
+ wf_binds_rb: dict[str, set] = {}
246
+ wf_contains: dict[str, set] = {}
247
+ cc_binds_cs: dict[str, set] = {}
248
+ for edge in _load_evidence_edges(out_root):
249
+ kind, src, tgt = edge.get("kind"), edge.get("source_fqdn"), edge.get("target_fqdn")
250
+ if not src or not tgt:
251
+ continue
252
+ if kind == "WF_BINDS_RB":
253
+ wf_binds_rb.setdefault(src, set()).add(tgt)
254
+ elif kind == "WF_CONTAINS_NODE":
255
+ wf_contains.setdefault(src, set()).add(tgt)
256
+ elif kind == "CC_BINDS_CS":
257
+ cc_binds_cs.setdefault(src, set()).add(tgt)
258
+
259
+ def bindings_for(path: str, store_name: str) -> list[dict[str, Any]]:
260
+ bindings: list[dict[str, Any]] = []
261
+ for (rb_fqdn, cs_fqdn), declared_paths in sorted(rb_paths.items()):
262
+ if path not in declared_paths:
263
+ continue
264
+ # Which contracts consume *this* store, not every store the binding reaches. A binding
265
+ # declared through a storage structure reaches every path that structure owns, so
266
+ # asking the binding alone answers "which contracts touch this domain's storage" and
267
+ # reports identical consumers for three different stores. A contract names the store
268
+ # each of its steps uses, and that is the fact being asked for.
269
+ # Scoped to what this binding actually reaches: the workflows bound to this RB,
270
+ # the contracts they contain, and of those the ones binding this capability.
271
+ rb_workflows = {wf for wf, rbs in wf_binds_rb.items() if rb_fqdn in rbs}
272
+ candidates = {cc for wf in rb_workflows
273
+ for cc in wf_contains.get(wf, set())
274
+ if cs_fqdn in cc_binds_cs.get(cc, set())}
275
+ if len(declared_paths) > 1:
276
+ # The binding reaches every store its structure owns, so it cannot say which one a
277
+ # contract used; the contract's own step declaration can. Filtering only here keeps
278
+ # a binding naming one concrete path — where the path *is* the store — answering for
279
+ # contracts that never needed to name it.
280
+ candidates = {cc for cc in candidates
281
+ if store_name in cc_stores.get(cc, set())}
282
+ consumer_ccs = sorted(candidates)
283
+ workflows = sorted({wf for wf in rb_workflows
284
+ if wf_contains.get(wf, set()) & candidates})
285
+ bindings.append({
286
+ "rb": rb_fqdn,
287
+ "cs": cs_fqdn,
288
+ "workflows": workflows,
289
+ "consumer_ccs": consumer_ccs,
290
+ })
291
+ return bindings
292
+
293
+ indexed = {
294
+ key: {
295
+ "store": store["store"],
296
+ "domain": store["domain"],
297
+ "declarations": [
298
+ {**declaration, "bindings": bindings_for(declaration["path"], store["store"])}
299
+ for declaration in store["declarations"]
300
+ ],
301
+ }
302
+ for key, store in stores.items()
303
+ }
304
+
305
+ return {
306
+ "schema_version": SCHEMA_VERSION,
307
+ "generated_by": "snapshot_assembler",
308
+ "store_count": len(indexed),
309
+ "stores": dict(sorted(indexed.items())),
310
+ }
311
+
312
+
313
+ def _declared_stores(docs: dict[str, dict]) -> dict[str, dict[str, Any]]:
314
+ """Stores declared via core.entity_stores in storage STRUCTUREs, keyed '<domain>::<STORE>'.
315
+
316
+ A store name may be declared by more than one storage STRUCTURE in a domain — with the same
317
+ path (a shared store) or different paths (per-subdomain stores sharing a name). Each
318
+ declaration is recorded as the protocol states it: no merging, no policing.
319
+ """
320
+ stores: dict[str, dict[str, Any]] = {}
321
+ for fqdn, doc in docs.items():
322
+ if doc.get("artifact_type") != "STRUCTURE":
323
+ continue
324
+ core = doc.get("frontmatter", {}).get("core", {})
325
+ entity_stores = core.get("entity_stores")
326
+ if not entity_stores:
327
+ continue
328
+ domain = core.get("domain") or fqdn.split("::", 1)[0]
329
+ for store_name in sorted(entity_stores):
330
+ declared = entity_stores[store_name]
331
+ entry = stores.setdefault(
332
+ f"{domain}::{store_name}",
333
+ {"store": store_name, "domain": domain, "declarations": []},
334
+ )
335
+ entry["declarations"].append({
336
+ "path": declared.get("path", ""),
337
+ "description": declared.get("description", ""),
338
+ "declared_by": fqdn,
339
+ })
340
+ for entry in stores.values():
341
+ entry["declarations"].sort(key=lambda d: (d["path"], d["declared_by"]))
342
+ return stores
343
+
344
+
345
+ def _cc_declared_stores(docs: dict[str, dict]) -> dict[str, set[str]]:
346
+ """CC fqdn → the store names its pipeline steps declare.
347
+
348
+ A contract states the store each side-effect step reaches. Nothing else in the composition
349
+ records which store a contract uses: the runtime binding names a capability and a structure,
350
+ and the evidence graph records that a contract binds a capability — neither says which of the
351
+ structure's stores the contract writes.
352
+ """
353
+ out: dict[str, set[str]] = {}
354
+ for fqdn, doc in docs.items():
355
+ if doc.get("artifact_type") != "CC":
356
+ continue
357
+ pipeline = doc.get("frontmatter", {}).get("core", {}).get("pipeline") or []
358
+ named = {step.get("store") for step in pipeline
359
+ if isinstance(step, dict) and step.get("store")}
360
+ if named:
361
+ out[fqdn] = named
362
+ return out
363
+
364
+
365
+ def _rb_store_paths(docs: dict[str, dict]) -> dict[tuple[str, str], set[str]]:
366
+ """(RB fqdn, CS fqdn) → the data paths that binding reaches.
367
+
368
+ A binding declares where its capability writes in one of two ways, and reading only the first
369
+ left this join blind to fourteen of the composition's fifteen stores:
370
+
371
+ policy.path one concrete path, with the data-root template prefix stripped
372
+ policy.structure a storage STRUCTURE, whose `entity_stores` declare every path it owns
373
+
374
+ The second is what every pipeline-authored domain uses, and the reference workload besides;
375
+ only `ai_governance` names paths in its policies. Resolving just the concrete form meant
376
+ `si.store.consumers` answered for that one domain and reported no consumer for every other
377
+ store in the composition — including stores three contracts demonstrably write.
378
+
379
+ A binding whose policy declares neither binds no store (CS_CLOCK_V0 under any RB is the
380
+ standing example) and contributes nothing to the join.
381
+ """
382
+ paths: dict[tuple[str, str], set[str]] = {}
383
+ for fqdn, doc in docs.items():
384
+ if doc.get("artifact_type") != "RB":
385
+ continue
386
+ core = doc.get("frontmatter", {}).get("core", {})
387
+ bindings = core.get("bindings", {})
388
+ for cs_fqdn in sorted(bindings):
389
+ policy = bindings[cs_fqdn].get("policy") or {}
390
+ declared: set[str] = set()
391
+
392
+ concrete = policy.get("path")
393
+ if concrete:
394
+ if concrete.startswith(_DATA_ROOT_TEMPLATE):
395
+ concrete = concrete[len(_DATA_ROOT_TEMPLATE):]
396
+ declared.add(concrete)
397
+
398
+ # A binding may declare its structure, or lean on the one the runtime binding declares
399
+ # for all of them. Reading only the per-binding form left every store whose binding
400
+ # carries an empty policy unreachable, though the RB says plainly where it writes.
401
+ # A binding may declare its structure, or lean on the one the runtime binding declares
402
+ # for all of them — but only when it names no path of its own. A binding that names one
403
+ # concrete path has already said where it writes, and adding its structure's other
404
+ # paths on top would make an unambiguous binding look like it reached them all.
405
+ structure = policy.get("structure") or (None if declared else core.get("storage_structure"))
406
+ if structure:
407
+ entity_stores = ((docs.get(structure) or {})
408
+ .get("frontmatter", {}).get("core", {}).get("entity_stores") or {})
409
+ declared.update(store.get("path") for store in entity_stores.values()
410
+ if isinstance(store, dict) and store.get("path"))
411
+
412
+ if declared:
413
+ paths[(fqdn, cs_fqdn)] = declared
414
+ return paths
415
+
416
+
417
+ def write_index(out_root: Path, rel_path: str, content: dict[str, Any]) -> Path:
418
+ path = out_root / rel_path
419
+ path.parent.mkdir(parents=True, exist_ok=True)
420
+ path.write_text(json.dumps(content, indent=2, sort_keys=True) + "\n", encoding="utf-8")
421
+ return path
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.4
2
+ Name: pgc-assembler
3
+ Version: 2.0.0
4
+ Summary: PGC snapshot assembler — composes compiled projections into a manifest-pinned executable snapshot (import package: assembler)
5
+ Author-email: Bhash Ganti <bachipeachy@gmail.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://omnibachi.org/
8
+ Project-URL: Repository, https://github.com/protocol-governed-computing/snapshot_assembler
9
+ Project-URL: Standard, https://doi.org/10.5281/zenodo.22150616
10
+ Keywords: protocol-governed-computing,pgc,snapshot,assembler,manifest
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Topic :: Software Development :: Build Tools
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ License-File: NOTICE
23
+ Dynamic: license-file
24
+
25
+ # snapshot_assembler
26
+
27
+ **Protocol-Governed Computing — snapshot assembler** (import package: `assembler`).
28
+
29
+ Composes each domain's compiled projections (from the protocol compiler) into one **executable
30
+ snapshot** with a content-derived, **manifest-pinned identity**. The runtime consumes only the
31
+ assembled snapshot — never an individual repo's compiled layout.
32
+
33
+ > The assembly contract — what a compiled projection must look like for the assembler to
34
+ > accept it — is governed by the standard, not by this repository.
35
+
36
+ ```
37
+ repos → protocol_compiler → each repo's compiled/ projections
38
+ → snapshot_assembler → protocol-governed-computing/snapshot/ → runtime warm reboot
39
+ ```
40
+
41
+ The platform is an ordinary member of the composition — no singleton branch. The composition
42
+ currently assembles **seven domains**: `platform`, `workload`, `inspection`, `transformation`,
43
+ and the business domains `ai_governance`, `blockchain` and `book_library_mgmt`.
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ pip install pgc-assembler
49
+ ```
50
+
51
+ Once installed:
52
+
53
+ ```bash
54
+ snapshot_assembler --help
55
+ ```
56
+
57
+ ## Use
58
+
59
+ ```bash
60
+ # assemble sibling platform compiled/ -> sibling snapshot/
61
+ ./assemble.sh
62
+
63
+ # explicit / multi-source (future domains)
64
+ ./assemble.sh --source /abs/software_governance/snapshot/compiled --out /abs/protocol-governed-computing/snapshot
65
+
66
+ # module form
67
+ PYTHONPATH=. python -m assembler.cli assemble --source <compiled_root> --out <snapshot_dir>
68
+ PYTHONPATH=. python -m assembler.cli verify --out <snapshot_dir>
69
+ ```
70
+
71
+ ## Product
72
+
73
+ ```
74
+ protocol-governed-computing/snapshot/
75
+ manifest.json # COMMITTED — the identity + root of trust
76
+ tokenized/<domain>/ # regenerated build product (gitignored)
77
+ trust/<domain>/ # regenerated build product (gitignored)
78
+ vocabulary/<domain>/ # regenerated build product (gitignored)
79
+ ```
80
+
81
+ - **`manifest.json` is the committed identity record.** The assembled projections are regenerated
82
+ build products whose contents MUST match the manifest during warm reboot.
83
+ - **`composite_hash`** is content-derived over the identity view of `domains[]` (per-domain
84
+ projection/attestation/graph hashes). Provenance and timestamps are excluded → same inputs +
85
+ same compiler + same assembler ⇒ same identity.
86
+
87
+ ## Composition conformance
88
+
89
+ Assembly does not end at the manifest. **Composition Conformance** is the lifecycle phase after it:
90
+ rules that can only be asked of the whole — the ones a single domain build contains no evidence for.
91
+ It runs on every assemble and its result is written to `conformance/composition.json`, so a snapshot
92
+ carries the record of having been judged as a composition rather than as a pile of domains.
93
+
94
+ ## Scope
95
+
96
+ - **Copy and pin:** each domain's projections are copied, hashes are lifted from compiler output, the
97
+ manifest is written, and round-trip `verify` runs after every `assemble`.
98
+ - **Vocabulary address-space reconciliation** remains the deferred real job. Collision detection is
99
+ enforced today; composing the reconciled forward/reverse maps across domains is not yet done, and
100
+ the composition has grown past the one-domain case that made it postponable.
101
+
102
+ ## License
103
+
104
+ Apache-2.0.
105
+
106
+ ---
107
+
108
+ ## The package family
109
+
110
+ | Package | Repository | Role |
111
+ |---|---|---|
112
+ | `pgc-compiler` | `protocol_compiler` | declarations → compiled projections |
113
+ | `pgc-assembler` | `snapshot_assembler` | projections → sealed snapshot |
114
+ | `pgc-runtime` | `protocol_runtime` | snapshot → governed execution |
115
+ | `pgc-inspector` | `snapshot_inspector` | snapshot → read-only inspection |
116
+ | `pgc-transformation` | `transformation` | change request → protocol artifacts |
117
+ | `pgc-governance` | `software_governance` | the governance surface and its capability implementations |
118
+ | `pgc-workloads` | `conformance_workloads` | the workloads that make conformance observable |
119
+ | `pgc-domains` | `business_domains` | the business domain implementations the composed snapshot binds |
120
+
121
+ `pip install pgc` brings in the whole family.
122
+
123
+ **Installing the toolchain is one of two steps.** The compiler resolves the governance surface from
124
+ `PGC_PLATFORM_ROOT` — fail-hard, cwd-independent, zero inference — so the *declarations* come from a
125
+ repository you point at, never from a wheel. A registry inside a package would be a second governance
126
+ surface competing with the repository's, and a build could then be governed by a stale copy.
127
+
128
+ ```bash
129
+ git clone https://github.com/protocol-governed-computing/software_governance
130
+ export PGC_PLATFORM_ROOT=$PWD/software_governance
131
+ pgc # reports what is installed and whether the anchor resolves
132
+ ```
133
+
134
+ `PGC_BUILD_ROOT` (compiled output, keeping the governance repo read-only) and `PGC_DOMAIN_ROOTS`
135
+ (additional domains contributing their own `registry/structures`) are optional.
136
+
137
+ **Versioning.** Two schemes, and the published version follows the second.
138
+
139
+ - **Internal** — each repository's `VERSION` file, a monotonic composition ordinal. PGC versions the
140
+ composition rather than each repo: they release together and the governance closure forces lockstep,
141
+ so the ordinal names which composition a repo belongs to. Development happens on `dev/<N>` and each
142
+ cycle is tagged `release-<N>`. This is not published.
143
+ - **Public** — `PUBLIC_VERSION`, tagged on every component repository. The platform is at **`v2`**.
144
+
145
+ **The published version is the public one: `v2` is `2.0.0`.** The standard the packages implement is a
146
+ separate artifact on its own track and is not this number.
147
+
148
+ The standard these packages implement is published separately: https://doi.org/10.5281/zenodo.22150616
@@ -0,0 +1,13 @@
1
+ assembler/VERSION,sha256=mpKtvAzuOO9ljHHOGxv4xlZo8Wa_shNkTIlcyxrQeiU,3
2
+ assembler/__init__.py,sha256=W7Z98sqFkwwevJmmQa476GJ974aEajPD2d1epyZAqw0,1285
3
+ assembler/cli.py,sha256=nThjf3vwz1qmn4FZeG4c5uMBuSdaM6wtpcvp8W7L9D0,4690
4
+ assembler/conformance.py,sha256=zi2uDGTsMUdkoEGGHtdoP2dIQMimpHJ_MHDw2Qn5vUM,10597
5
+ assembler/core.py,sha256=55Sfd3HQxSKP4fW2X8a_ePSPdrVrC-GDsRbdKONY_yE,37010
6
+ assembler/indexes.py,sha256=e-0LEqMnE48seVIOJy9tvhDpFPFmTAlSk3FjADR2b8g,19922
7
+ pgc_assembler-2.0.0.dist-info/licenses/LICENSE,sha256=wf4s-Px76q2zHB2b8Gbvuu4ULua8AMds2IY01MkBn0A,2544
8
+ pgc_assembler-2.0.0.dist-info/licenses/NOTICE,sha256=nE4--l6r3rMCz0nVaFyFohZtY4NYADnrhcS9K2pdNr0,407
9
+ pgc_assembler-2.0.0.dist-info/METADATA,sha256=Zn2tY9ACsi1TGiNrzR2Hx1-eKXo5KTmHNufseUNgmXM,6651
10
+ pgc_assembler-2.0.0.dist-info/WHEEL,sha256=oFyoA3ogC3W5C0Id4sPJabNAAnDnz8g-u7AUl41-31I,90
11
+ pgc_assembler-2.0.0.dist-info/entry_points.txt,sha256=LHtd453be3lrx7dCgM52JKTLQTQAYldM3KqZzc-MI9Q,58
12
+ pgc_assembler-2.0.0.dist-info/top_level.txt,sha256=MjECpqT3FbgJ3vnCel7GSPTAy5WBRGZS6XNWlyTpqoI,10
13
+ pgc_assembler-2.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (2.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ snapshot_assembler = assembler.cli:main
@@ -0,0 +1,67 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Copyright 2026 Bhash Ganti aka Bachi
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
18
+
19
+
20
+ ------------------------
21
+ FULL LICENSE TEXT BELOW
22
+ ------------------------
23
+
24
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
25
+
26
+ 1. Definitions.
27
+ "License" shall mean the terms and conditions for use, reproduction,
28
+ and distribution as defined by Sections 1 through 9 of this document.
29
+ "Licensor" shall mean the copyright owner.
30
+ "Legal Entity" shall mean the union of the acting entity and all other
31
+ entities that control, are controlled by, or are under common control.
32
+ "You" shall mean an individual or Legal Entity exercising permissions.
33
+ "Source" form shall mean the preferred form for making modifications.
34
+ "Object" form shall mean any form resulting from mechanical transformation.
35
+ "Work" shall mean the work of authorship.
36
+ "Derivative Works" shall mean any work based on the Work.
37
+ "Contribution" shall mean any work intentionally submitted.
38
+ "Contributor" shall mean Licensor and any individual submitting Contributions.
39
+
40
+ 2. Grant of Copyright License.
41
+ Each Contributor grants You a perpetual, worldwide, non-exclusive,
42
+ no-charge, royalty-free copyright license.
43
+
44
+ 3. Grant of Patent License.
45
+ Each Contributor grants a patent license to make, use, sell, etc.
46
+
47
+ 4. Redistribution.
48
+ You may reproduce and distribute copies provided that:
49
+ - You include a copy of this License
50
+ - You retain notices
51
+ - You state modifications
52
+ - You include NOTICE file if present
53
+
54
+ 5. Submission of Contributions.
55
+ Contributions are under this License unless stated otherwise.
56
+
57
+ 6. Trademarks.
58
+ This License does not grant trademark rights.
59
+
60
+ 7. Disclaimer of Warranty.
61
+ Provided "AS IS", without warranties.
62
+
63
+ 8. Limitation of Liability.
64
+ No liability for damages.
65
+
66
+ 9. Accepting Warranty or Additional Liability.
67
+ You may offer support/warranty on your own behalf only.
@@ -0,0 +1,11 @@
1
+ PGS — Protocol-Governed Systems
2
+ Copyright 2026 Bhash Ganti aka Bachi
3
+
4
+ This project introduces a protocol-first execution model in which:
5
+
6
+ - Behavior is declared in protocol artifacts
7
+ - Execution is performed by a deterministic runtime
8
+ - Capability implementations are bound at compile time
9
+ - Governance is enforced through invariants and assertions
10
+
11
+ Extensibility is achieved by declaration, not refactor.
@@ -0,0 +1 @@
1
+ assembler