build(agent): molt-x#ed374b iteration
This commit is contained in:
parent
3f315770ea
commit
90c131b215
|
|
@ -0,0 +1,21 @@
|
|||
node_modules/
|
||||
.npmrc
|
||||
.env
|
||||
.env.*
|
||||
__tests__/
|
||||
coverage/
|
||||
.nyc_output/
|
||||
dist/
|
||||
build/
|
||||
.cache/
|
||||
*.log
|
||||
.DS_Store
|
||||
tmp/
|
||||
.tmp/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.venv/
|
||||
venv/
|
||||
*.egg-info/
|
||||
.pytest_cache/
|
||||
READY_TO_PUBLISH
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# AGENTS
|
||||
|
||||
Architecture overview for PromptLedger MVP
|
||||
|
||||
- Language/Stack: Python 3.8+ with standard library; packaging metadata via pyproject.toml
|
||||
- Core modules:
|
||||
- promptledger_verifiable_provenance_and_l: LocalProvenanceBlock, MerkleAuditLog, DeltaSync, Adapters
|
||||
- Data model:
|
||||
- LocalProvenanceBlock records a creative step with author, tool, action, metadata, license, timestamp, and a crypto-like signature
|
||||
- Provenance storage:
|
||||
- MerkleAuditLog keeps blocks and a Merkle root for tamper-evidence
|
||||
- Synchronization: DeltaSync provides delta-exports/imports of provenance blocks for offline-first operation
|
||||
- Adapters:
|
||||
- BlenderAdapter, FigmaAdapter emit provenance blocks for their respective tools
|
||||
- Governance: Draft governance scaffolding in code comments and API design; real RBAC and policy templates to extend later
|
||||
|
||||
- Testing:
|
||||
- pytest-based tests under tests/
|
||||
- Build/publish: python3 -m build; test.sh will run tests and build, per repository requirements
|
||||
|
||||
How to run locally:
|
||||
- Install dependencies (only standard library for MVP): no extra deps required
|
||||
- Run unit tests: pytest
|
||||
- Build package: python3 -m build
|
||||
|
||||
Note: This is a minimal MVP to bootstrap the architecture. Future work will introduce real cryptographic PKI, policy engines, delta-sync optimizations, and cross-tool adapters.
|
||||
22
README.md
22
README.md
|
|
@ -1,3 +1,23 @@
|
|||
# promptledger-verifiable-provenance-and-l
|
||||
|
||||
A cross-tool, offline-first ledger that records prompts, model versions, seeds, parameter configurations, sources of assets, licensing terms, and outputs in a tamper-evident, auditable log. It integrates a schema registry for prompt contracts and a d
|
||||
This project provides a minimal MVP for a cross-tool, offline-first provenance ledger intended for generative AI creative workflows. It demonstrates a tamper-evident log of prompts, model configurations, assets, licenses, and outputs, with a simple Merkle-based audit trail and delta-sync scaffolding. Adapters map Blender and Figma provenance into a canonical model.
|
||||
|
||||
How to run locally
|
||||
- Prereqs: Python 3.8+, pip, and build tooling (setuptools, wheel)
|
||||
- Build metadata: pyproject.toml (name: promptledger-verifiable-provenance-and-l)
|
||||
- Run tests: ./test.sh (requires pytest; should pass without extra dependencies)
|
||||
- Build package: ./test.sh (will run python3 -m build)
|
||||
|
||||
Project structure (核心)
|
||||
- promptledger_verifiable_provenance_and_l/: Python package containing:
|
||||
- LocalProvenanceBlock: a single provenance step with author, tool, action, metadata, and license
|
||||
- MerkleAuditLog: a simple, tamper-evident log built on a Merkle tree of blocks
|
||||
- DeltaSync: lightweight export/import of provenance deltas for offline-first operation
|
||||
- BlenderAdapter, FigmaAdapter: sample adapters emitting provenance blocks
|
||||
|
||||
Notes
|
||||
- This is a minimal MVP intended to bootstrap the architecture. Real-world deployment would require robust crypto (PKI), policy engines, RBAC, and robust delta-sync guarantees with privacy protections.
|
||||
|
||||
License: MIT
|
||||
|
||||
READY_TO_PUBLISH marker is created when the repo is ready to publish.
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
from promptledger_verifiable_provenance_and_l import * # re-export for easier imports in tests
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
import json
|
||||
import hashlib
|
||||
import time
|
||||
from typing import List, Dict, Any
|
||||
import hmac
|
||||
|
||||
# Simple, self-contained MVP: local provenance ledger with a Merkle audit log
|
||||
HASH_ALGO = hashlib.sha256
|
||||
SIGNING_KEY = b"demo-secret-key" # In a real product, use a proper KMS/PKI; kept here for MVP.
|
||||
|
||||
|
||||
def _serialize(obj: Any) -> bytes:
|
||||
return json.dumps(obj, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
||||
|
||||
|
||||
def _hash_block(block: Dict[str, Any]) -> str:
|
||||
data = _serialize(block)
|
||||
return HASH_ALGO(data).hexdigest()
|
||||
|
||||
|
||||
def _sign(data: bytes) -> str:
|
||||
# Simple HMAC-based signature for MVP (not a full cryptographic signature scheme)
|
||||
return hmac.new(SIGNING_KEY, data, HASH_ALGO).hexdigest()
|
||||
|
||||
|
||||
class LocalProvenanceBlock:
|
||||
def __init__(self, author: str, tool: str, action: str, metadata: Dict[str, Any], license_: str):
|
||||
self.author = author
|
||||
self.tool = tool
|
||||
self.action = action # e.g., "create", "modify"
|
||||
self.metadata = metadata
|
||||
self.license = license_
|
||||
self.timestamp = time.time()
|
||||
self.block_id = hashlib.sha256(f"{author}:{tool}:{action}:{self.timestamp}".encode("utf-8")).hexdigest()
|
||||
self.signature = None # to be filled by ledger when appended
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"block_id": self.block_id,
|
||||
"author": self.author,
|
||||
"tool": self.tool,
|
||||
"action": self.action,
|
||||
"metadata": self.metadata,
|
||||
"license": self.license,
|
||||
"timestamp": self.timestamp,
|
||||
"signature": self.signature,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"LocalProvenanceBlock(id={self.block_id})"
|
||||
|
||||
|
||||
class MerkleAuditLog:
|
||||
def __init__(self):
|
||||
self.blocks: List[Dict[str, Any]] = []
|
||||
self.merkle_root: str = ""
|
||||
|
||||
def append(self, block: LocalProvenanceBlock) -> None:
|
||||
blob = block.to_dict()
|
||||
blob["signature"] = block.signature
|
||||
self.blocks.append(blob)
|
||||
# Recompute signature and Merkle root for simplicity on each append
|
||||
block_data = self._compute_hash_chain()
|
||||
self.merkle_root = block_data[0] if isinstance(block_data, (list, tuple)) else block_data
|
||||
|
||||
def _compute_hash_chain(self) -> List[str]:
|
||||
leaves = [HASH_ALGO(_serialize(b)).hexdigest() for b in self.blocks]
|
||||
if not leaves:
|
||||
return [""]
|
||||
# Simple binary Merkle; pad with last leaf if needed
|
||||
current = leaves
|
||||
while len(current) > 1:
|
||||
next_level = []
|
||||
for i in range(0, len(current), 2):
|
||||
a = current[i]
|
||||
b = current[i + 1] if i + 1 < len(current) else a
|
||||
next_level.append(HASH_ALGO((a + b).encode("utf-8")).hexdigest())
|
||||
current = next_level
|
||||
return current
|
||||
|
||||
def get_root(self) -> str:
|
||||
return self.merkle_root
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"root": self.merkle_root,
|
||||
"count": len(self.blocks),
|
||||
"blocks": self.blocks,
|
||||
}
|
||||
|
||||
|
||||
class DeltaSync:
|
||||
def __init__(self, log: MerkleAuditLog):
|
||||
self.log = log
|
||||
self.synced_index = 0
|
||||
|
||||
def create_delta(self) -> Dict[str, Any]:
|
||||
# Return the delta since last sync
|
||||
delta_blocks = self.log.blocks[self.synced_index :]
|
||||
delta = {
|
||||
"start_index": self.synced_index,
|
||||
"count": len(delta_blocks),
|
||||
"blocks": delta_blocks,
|
||||
"root": self.log.get_root(),
|
||||
}
|
||||
self.synced_index = len(self.log.blocks)
|
||||
return delta
|
||||
|
||||
def apply_delta(self, delta: Dict[str, Any]) -> None:
|
||||
# For MVP: simply set internal state to the provided delta's root and blocks
|
||||
# In real use, this would verify delta provenance and apply safely
|
||||
for b in delta.get("blocks", []):
|
||||
if b not in self.log.blocks:
|
||||
self.log.blocks.append(b)
|
||||
self.log.merkle_root = delta.get("root", self.log.get_root())
|
||||
|
||||
def is_in_sync_with(self, other_root: str) -> bool:
|
||||
return self.log.get_root() == other_root
|
||||
|
||||
|
||||
class Adapter:
|
||||
def __init__(self, author: str, license_: str = "CC-BY-4.0"):
|
||||
self.author = author
|
||||
self.license = license_
|
||||
|
||||
def emit(self) -> LocalProvenanceBlock:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class BlenderAdapter(Adapter):
|
||||
def emit(self) -> LocalProvenanceBlock:
|
||||
block = LocalProvenanceBlock(
|
||||
author=self.author,
|
||||
tool="Blender",
|
||||
action="create_asset",
|
||||
metadata={"asset_type": "3d_model", "scene": "SampleScene"},
|
||||
license_=self.license,
|
||||
)
|
||||
return block
|
||||
|
||||
|
||||
class FigmaAdapter(Adapter):
|
||||
def emit(self) -> LocalProvenanceBlock:
|
||||
block = LocalProvenanceBlock(
|
||||
author=self.author,
|
||||
tool="Figma",
|
||||
action="update_design",
|
||||
metadata={"frame": "HeroSection", "pages": ["Landing", "Docs"]},
|
||||
license_=self.license,
|
||||
)
|
||||
return block
|
||||
|
||||
|
||||
def attach_signature(block: LocalProvenanceBlock) -> None:
|
||||
data = block.to_dict()
|
||||
# Sign the block serialization excluding signature itself
|
||||
data.pop("signature", None)
|
||||
sig = _sign(_serialize(data))
|
||||
block.signature = sig
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=40.6.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "promptledger-verifiable-provenance-and-l"
|
||||
version = "0.1.0"
|
||||
description = "MVP: offline-first provenance ledger with adapters for Blender and Figma"
|
||||
requires-python = ">=3.8"
|
||||
readme = "README.md"
|
||||
license = {text = "MIT"}
|
||||
authors = [ { name = "OpenCode Assistant" } ]
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Topic :: Software Development :: Libraries"
|
||||
]
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "Setting up package in editable mode..."
|
||||
python3 -m pip install -e .
|
||||
|
||||
echo "Running tests..."
|
||||
pytest -q
|
||||
|
||||
echo "Building package..."
|
||||
python3 -m build
|
||||
|
||||
echo "All tests passed and package built."
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import json
|
||||
from promptledger_verifiable_provenance_and_l import (
|
||||
LocalProvenanceBlock,
|
||||
MerkleAuditLog,
|
||||
DeltaSync,
|
||||
BlenderAdapter,
|
||||
FigmaAdapter,
|
||||
attach_signature,
|
||||
)
|
||||
|
||||
|
||||
def test_merkle_audit_log_basic():
|
||||
log = MerkleAuditLog()
|
||||
blk1 = LocalProvenanceBlock("alice", "Blender", "create_asset", {"asset_type": "mesh"}, "CC-BY-4.0")
|
||||
attach_signature(blk1)
|
||||
log.append(blk1)
|
||||
|
||||
blk2 = LocalProvenanceBlock("bob", "Figma", "update_design", {"frame": "Intro"}, "MIT")
|
||||
attach_signature(blk2)
|
||||
log.append(blk2)
|
||||
|
||||
root = log.get_root()
|
||||
assert isinstance(root, str) and len(root) > 0
|
||||
# Recompute by creating a new log with same blocks and ensure root matches
|
||||
log2 = MerkleAuditLog()
|
||||
log2.append(blk1)
|
||||
log2.append(blk2)
|
||||
assert log2.get_root() == root
|
||||
|
||||
|
||||
def test_delta_sync_roundtrip():
|
||||
log = MerkleAuditLog()
|
||||
delta_sync = DeltaSync(log)
|
||||
|
||||
a = LocalProvenanceBlock("carol", "Blender", "create_asset", {"asset_type": "texture"}, "CC0")
|
||||
attach_signature(a)
|
||||
log.append(a)
|
||||
|
||||
delta1 = delta_sync.create_delta()
|
||||
# Simulate recipient with empty log getting delta
|
||||
recipient_log = MerkleAuditLog()
|
||||
recipient_sync = DeltaSync(recipient_log)
|
||||
recipient_sync.apply_delta(delta1)
|
||||
assert recipient_log.get_root() == log.get_root()
|
||||
|
||||
|
||||
def test_adapters_emit_and_sign():
|
||||
blender = BlenderAdapter("dave")
|
||||
figma = FigmaAdapter("eve")
|
||||
|
||||
b = blender.emit()
|
||||
attach_signature(b)
|
||||
f = figma.emit()
|
||||
attach_signature(f)
|
||||
|
||||
assert b.tool == "Blender" and b.author == "dave"
|
||||
assert f.tool == "Figma" and f.author == "eve"
|
||||
Loading…
Reference in New Issue