Skip to content

Commit e400d01

Browse files
authored
Merge pull request #55 from egohygiene/codex/aether-social-surface-specs
feat: establish governed social-surface catalog skill
2 parents 68d5d82 + ace1c6b commit e400d01

43 files changed

Lines changed: 1821 additions & 23 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

aether_validator.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,10 @@ def package_rel(path: Path) -> str:
323323
if path.suffix.lower() not in {".md", ".json", ".txt", ".yaml", ".yml", ".sh", ".py", ".js", ".mjs"}:
324324
continue
325325
text = path.read_text(encoding="utf-8")
326-
if "library/organization/" in text:
326+
# The generated manifest may name the canonical builder for provenance.
327+
# That is not a runtime reference and does not make the installed
328+
# package depend on canonical source paths.
329+
if "library/organization/" in text and path != manifest_path:
327330
diagnostics.append(Diagnostic(
328331
rule_id="AETHER_PORTABLE_015",
329332
severity="error",
@@ -1755,6 +1758,7 @@ def command_distribution_build(args: argparse.Namespace) -> int:
17551758
root / "library" / "organization" / "specs" / "build-distributions.py",
17561759
root / "library" / "organization" / "skills" / "build-distributions.py",
17571760
root / "library" / "organization" / "agents" / "build-projections.py",
1761+
root / "catalog" / "social-surfaces" / "build-distribution.py",
17581762
]
17591763
exit_code = EXIT_OK
17601764
for script_path in scripts:

catalog/PROVENANCE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Aether already has mature source-specific catalogs:
99
- `catalog/first-party/catalog.v1.json` for canonical specifications and skills;
1010
- `library/organization/agents/catalog.json` for reusable agent source;
1111
- `catalog/external/approved-skills.v1.json` for reviewed external skills.
12+
- `catalog/external/source-candidates.v1.json` for provenance-preserving external sources that are not approved for redistribution or publication.
1213

1314
Those sources evolved at different times and therefore use different field names. They remain valid compatibility/source catalogs. `catalog/provenance_model.py` projects them into one normalized model for policy decisions without making a provider format or a generated report canonical.
1415

catalog/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ provenance, lifecycle, and distribution.
99
- `fixtures/` — valid/invalid examples per schema
1010
- `first-party/catalog.v1.json` — canonical compatibility catalog for the current specification and skill corpus
1111
- `external/approved-skills.v1.json` — governed external skill catalog entries reconstructed from staged provenance
12+
- `external/source-candidates.v1.json` — non-publishable external source candidates, including their rights-review state
13+
- `social-surfaces/` — versioned social-surface contract, offline catalog, query tool, and deterministic distribution builder
1214
- `PROVENANCE.md` — shared lifecycle, trust, provenance, and stable-publication policy
1315
- `provenance_model.py` — deterministic normalized provenance projection across first-party specs/skills/agents and reviewed external artifacts
1416
- `reports/first-party-coverage.v1.json` — coverage report for the canonical spec/skill corpus
@@ -62,6 +64,7 @@ First-party and external artifacts are never conflated:
6264

6365
- first-party canonical source receives `first-party` trust;
6466
- reviewed external source preserves its upstream repository/revision, license, digest, and external trust classification;
67+
- a pending external source candidate remains external, non-publishable, and cannot silently populate a stable first-party catalog;
6568
- external review does not silently reclassify content as first-party;
6669
- restricted, unknown, or untrusted source cannot enter stable publication.
6770

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"schema_version": "aether.external-source-candidates/v1",
3+
"entries": [
4+
{
5+
"id": "external/media-cheat-sheet-social-surface-snapshot",
6+
"upstream_repository": "https://mediacheatsheet.com/",
7+
"upstream_ref": {
8+
"tag": "snapshot-2026-08-28",
9+
"commit": "unknown"
10+
},
11+
"upstream_skill_path": "social-media specification directory snapshot (not redistributed by Aether)",
12+
"source_type": "specification",
13+
"acquisition_method": "manual",
14+
"upstream_license": {
15+
"spdx_or_unknown": "unknown",
16+
"notice_path_or_unknown": "https://mediacheatsheet.com/terms/"
17+
},
18+
"content_hash": {
19+
"algorithm": "sha256-bytes",
20+
"value": "13a44970b5d1fd7a232eabc6a2abe42e7ba5c4a6a68e21484717f2b70509a0ac"
21+
},
22+
"acquired_at": "2026-08-28T17:07:39Z",
23+
"reviewed_at": "unknown",
24+
"review_state": "pending",
25+
"trust_classification": "unknown",
26+
"compatibility": {
27+
"required_tools": []
28+
},
29+
"executable_resources": "unknown",
30+
"consumer_profile": "human-review",
31+
"pinned_install_example": "not-installable: the supplied snapshot is an external source candidate and is not redistributed by Aether",
32+
"redistribution_permission": "unknown",
33+
"redistribution_status": "unverified"
34+
}
35+
]
36+
}

catalog/first-party/catalog.v1.json

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,51 @@
13071307
],
13081308
"title": "Skill Authoring"
13091309
},
1310+
{
1311+
"artifact_version": "1.0.0",
1312+
"compatibility": {
1313+
"required_tools": []
1314+
},
1315+
"dependencies": [],
1316+
"deprecation": {
1317+
"replacement_id": null
1318+
},
1319+
"description": "Select and explain pinned social-media or advertising creative surfaces from a provenance-governed offline catalog. Use when work needs platform placement, dimensions, media limits, safe zones, or organic-versus-advertising distinctions.",
1320+
"domain": "marketing",
1321+
"generated_distribution_paths": [
1322+
"dist/skills/social-surface-specs/SKILL.md"
1323+
],
1324+
"id": "skill/social-surface-specs",
1325+
"implements_specification_id": null,
1326+
"kind": "skill",
1327+
"license": "MIT",
1328+
"lifecycle": {
1329+
"state": "experimental"
1330+
},
1331+
"owner": {
1332+
"maintainers": [
1333+
"egohygiene"
1334+
],
1335+
"primary": "egohygiene"
1336+
},
1337+
"related_ids": [],
1338+
"release": {
1339+
"included": false,
1340+
"reason": "Only stable artifacts are releasable under catalog policy."
1341+
},
1342+
"scope": "organization",
1343+
"source_digest": {
1344+
"algorithm": "sha256-utf8-lf",
1345+
"value": "ba2d0baa2dc8ecbfe3f268ac64dcaf31d9ab2dccb111b2f2a8ff7f44c4441a49"
1346+
},
1347+
"source_path": "library/organization/skills/marketing/social-surface-specs/SKILL.md",
1348+
"supersedes_ids": [],
1349+
"tags": [
1350+
"marketing",
1351+
"skill"
1352+
],
1353+
"title": "Social Surface Specs"
1354+
},
13101355
{
13111356
"artifact_version": "1.0.0",
13121357
"compatibility": {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"schema_version": "aether.catalog-distribution-manifest/v1",
3+
"catalog_id": "example-catalog",
4+
"catalog_version": "1.0",
5+
"catalog_digest": {
6+
"algorithm": "sha256-utf8-lf",
7+
"value": "short"
8+
},
9+
"generated_paths": [],
10+
"generator": "",
11+
"publication": {
12+
"state": "published",
13+
"reason": ""
14+
}
15+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"schema_version": "aether.catalog-distribution-manifest/v1",
3+
"catalog_id": "catalog/example-catalog",
4+
"catalog_version": "1.0.0",
5+
"catalog_digest": {
6+
"algorithm": "sha256-utf8-lf",
7+
"value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
8+
},
9+
"generated_paths": [
10+
"dist/catalogs/example-catalog/catalog.v1.json"
11+
],
12+
"generator": "catalog/example/build.py",
13+
"publication": {
14+
"state": "blocked",
15+
"reason": "Review pending."
16+
}
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"schema_version": "aether.social-surface-catalog/v1",
3+
"catalog": {
4+
"id": "catalog/example-surfaces",
5+
"version": "not-semver"
6+
},
7+
"records": [
8+
{
9+
"id": "surface/Example",
10+
"use": "both"
11+
}
12+
]
13+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{
2+
"schema_version": "aether.social-surface-catalog/v1",
3+
"catalog": {
4+
"id": "catalog/example-surfaces",
5+
"version": "1.0.0",
6+
"lifecycle": {
7+
"state": "experimental"
8+
},
9+
"source_candidates": [
10+
"external/example-source"
11+
],
12+
"source_snapshot": {
13+
"source_url": "https://example.invalid/",
14+
"captured_at": "2026-08-28T00:00:00Z",
15+
"digest": {
16+
"algorithm": "sha256-bytes",
17+
"value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
18+
},
19+
"platform_count": 1,
20+
"record_count": 1,
21+
"template_count": 0
22+
},
23+
"rights_review": {
24+
"state": "pending",
25+
"reviewed_at": null,
26+
"decision_reference": null,
27+
"notes": "Review pending."
28+
},
29+
"release": {
30+
"included": false,
31+
"reason": "Review pending."
32+
}
33+
},
34+
"records": [
35+
{
36+
"id": "surface/example-organic-image",
37+
"platform": "Example",
38+
"placement": "Post",
39+
"use": "organic",
40+
"content_type": "post",
41+
"media_format": "image",
42+
"dimensions": null,
43+
"aspect_ratio": null,
44+
"file_types": null,
45+
"file_size_limit_bytes": null,
46+
"duration_limit_seconds": null,
47+
"safe_zone": {
48+
"state": "unknown",
49+
"insets_px": null,
50+
"notes": null
51+
},
52+
"verification": {
53+
"state": "unknown",
54+
"reviewed_at": null,
55+
"note": null
56+
},
57+
"source": {
58+
"candidate_id": "external/example-source",
59+
"url": "https://example.invalid/source",
60+
"label": "Example source",
61+
"captured_at": "2026-08-28T00:00:00Z",
62+
"snapshot_digest": {
63+
"algorithm": "sha256-bytes",
64+
"value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
65+
}
66+
},
67+
"lifecycle": {
68+
"state": "experimental"
69+
}
70+
}
71+
]
72+
}

catalog/provenance_model.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717

1818
ROOT = Path(__file__).resolve().parents[1]
1919
FIRST_PARTY_CATALOG = ROOT / "catalog" / "first-party" / "catalog.v1.json"
20-
EXTERNAL_CATALOG = ROOT / "catalog" / "external" / "approved-skills.v1.json"
20+
EXTERNAL_CATALOGS = (
21+
ROOT / "catalog" / "external" / "approved-skills.v1.json",
22+
ROOT / "catalog" / "external" / "source-candidates.v1.json",
23+
)
2124
AGENT_CATALOG = ROOT / "library" / "organization" / "agents" / "catalog.json"
2225
SCHEMA_PATH = ROOT / "catalog" / "schemas" / "aether.provenance-catalog.v1.schema.json"
2326
REPOSITORY_LICENSE = "MIT"
@@ -97,7 +100,7 @@ def _publishability(record: dict[str, Any]) -> tuple[bool, list[str]]:
97100

98101
if not revision or revision == "unknown" or REVISION_RE.fullmatch(revision) is None:
99102
reasons.append("source revision is not an immutable commit or semantic version")
100-
if digest.get("algorithm") not in {"sha256-utf8-lf", "sha256-tree"}:
103+
if digest.get("algorithm") not in {"sha256-utf8-lf", "sha256-tree", "sha256-bytes"}:
101104
reasons.append("source digest algorithm is unsupported")
102105
if not isinstance(digest.get("value"), str) or HEX_64_RE.fullmatch(digest["value"]) is None:
103106
reasons.append("source digest is missing or invalid")
@@ -272,15 +275,19 @@ def _external_lifecycle(entry: dict[str, Any]) -> dict[str, str]:
272275

273276
def build_external() -> dict[str, Any]:
274277
"""Build the normalized external provenance catalog."""
275-
source_catalog = load_json(EXTERNAL_CATALOG)
276-
entries = source_catalog.get("entries")
277-
if not isinstance(entries, list):
278-
raise ValueError(f"{EXTERNAL_CATALOG} entries must be an array")
278+
entries: list[dict[str, Any]] = []
279+
for catalog_path in EXTERNAL_CATALOGS:
280+
source_catalog = load_json(catalog_path)
281+
candidate_entries = source_catalog.get("entries")
282+
if not isinstance(candidate_entries, list):
283+
raise ValueError(f"{catalog_path} entries must be an array")
284+
for entry in candidate_entries:
285+
if not isinstance(entry, dict):
286+
raise ValueError(f"{catalog_path} entries must be objects")
287+
entries.append(entry)
279288

280289
records: list[dict[str, Any]] = []
281290
for entry in entries:
282-
if not isinstance(entry, dict):
283-
raise ValueError("external catalog entries must be objects")
284291
source_type = str(entry.get("source_type", "other"))
285292
if source_type not in {"skill", "specification", "agent", "prompt", "instruction"}:
286293
raise ValueError(f"unsupported external source_type: {source_type}")
@@ -402,12 +409,14 @@ def check(scope: str) -> list[str]:
402409
errors.append(f"missing canonical agent records: {missing}")
403410

404411
if scope in {"external", "all"}:
405-
external_catalog = load_json(EXTERNAL_CATALOG)
406-
expected_external = {
407-
str(entry.get("id"))
408-
for entry in external_catalog.get("entries", [])
409-
if isinstance(entry, dict)
410-
}
412+
expected_external = set()
413+
for catalog_path in EXTERNAL_CATALOGS:
414+
external_catalog = load_json(catalog_path)
415+
expected_external.update(
416+
str(entry.get("id"))
417+
for entry in external_catalog.get("entries", [])
418+
if isinstance(entry, dict)
419+
)
411420
present = {
412421
record["id"] for record in first["records"] if record.get("party") == "external"
413422
}

0 commit comments

Comments
 (0)