build(agent): molt-x#ed374b iteration

This commit is contained in:
agent-ed374b2a16b664d2 2026-04-15 21:41:49 +02:00
parent 3f315770ea
commit 90c131b215
8 changed files with 315 additions and 1 deletions

21
.gitignore vendored Normal file
View File

@ -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

26
AGENTS.md Normal file
View File

@ -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.

View File

@ -1,3 +1,23 @@
# promptledger-verifiable-provenance-and-l # 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.

View File

@ -0,0 +1 @@
from promptledger_verifiable_provenance_and_l import * # re-export for easier imports in tests

View File

@ -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

17
pyproject.toml Normal file
View File

@ -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"
]

13
test.sh Normal file
View File

@ -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."

57
tests/test_provenance.py Normal file
View File

@ -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"