diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 736bbe2..bd9417e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,6 +44,7 @@ jobs: python scripts/test_check_readme_links.py python scripts/audit_public_documentation.py --sources python scripts/test_identity_continuity_audit.py + python scripts/test_insonic_construction.py python skill/templates/test_brand_contract.py python skill/templates/test_messaging.py python skill/templates/test_brand_essentials.py @@ -87,6 +88,7 @@ jobs: run: | python scripts/export_approved_identity_proofs.py brands/i-heart-pr-tours dist/ci-approved-proofs/i-heart-pr-tours python scripts/export_approved_identity_proofs.py brands/local-companion dist/ci-approved-proofs/local-companion + python scripts/export_approved_identity_proofs.py brands/insonic dist/ci-approved-proofs/insonic - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 26.5.0 @@ -160,6 +162,7 @@ jobs: python scripts/test_check_readme_links.py python scripts/audit_public_documentation.py --sources python scripts/test_identity_continuity_audit.py + python scripts/test_insonic_construction.py python skill/templates/test_brand_contract.py python skill/templates/test_messaging.py python skill/templates/test_color_roles.py @@ -234,6 +237,7 @@ jobs: - name: Install audited WordPress fixture runner run: | npm ci --prefix scripts/wordpress-runtime --ignore-scripts + npm test --prefix scripts/wordpress-runtime npm audit --prefix scripts/wordpress-runtime --audit-level=moderate - name: Exercise pinned WordPress and PHP pairs run: | @@ -294,6 +298,7 @@ jobs: dist/glitchpad/ dist/go-schedule/ dist/i-heart-pr-tours/ + dist/insonic/ dist/local-companion/ dist/scruggs-tire-alignment/ dist/shruggietech/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 7033737..bd5f22e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to the Shruggie brand system are documented in this file. Th ## [Unreleased] +## [3.0.1] - 2026-10-03 + +### Added + +- Added S070 insonic 1.0.0, a ShruggieTech-owned voice and knowledge project, with approved three-form A1 masters, an independent Glacial blue palette, exact developer-facing messages and source-bound social composition. +- Included insonic in the official guideline, registry, download and release inventories. Twelve brands are hosted; nine owned brands receive release archives, while the existing three client kits remain hosted candidates. + +### Fixed + +- Aligned the type specimen's mark and name using measured ink centers, with regression coverage for asymmetric marks, descenders and supplied source preservation. +- Wrote outlined type specimens with UTF-8 without BOM and explicit LF on Windows. Existing approved logo geometry, font faces, social images and adapter APIs remain unchanged. +- Isolated page-QC temporary screenshots per run so concurrent kit builds cannot overwrite or truncate one another's images. +- Added a dependency-resolving insonic construction launcher for repository and standalone-kit use, preserving the canonical helper's approved bytes and exact generated paths. +- Replaced the pinned WordPress test runner's unused HTTP-cache dependency chain with an uncached built-in-fetch transport after the owner authorized remediation of GHSA-ch52-4w7c-c8xp. The required npm audit remains enforced; transport regressions cover repeated requests, cookies, downloads, errors and cancellation. This fixture change does not enter consumer kits. + ## [3.0.0] - 2026-09-29 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0449507..6420bf8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,6 +25,7 @@ Run the geometry unit tests, probe the toolchain, build every kit, and build the ```powershell .\.venv\Scripts\python skill/templates/test_glyphkit.py +.\.venv\Scripts\python scripts/test_insonic_construction.py .\.venv\Scripts\python skill/templates/test_brand_essentials.py .\.venv\Scripts\python specs/068-brand-essentials-docs-navigation/inventory.py .\.venv\Scripts\python skill/templates/test_interface_contract.py @@ -38,6 +39,11 @@ Run the geometry unit tests, probe the toolchain, build every kit, and build the .\.venv\Scripts\python scripts/build_all.py .\.venv\Scripts\python scripts/test_brand_essentials_delivery.py .\.venv\Scripts\python scripts/test_registry_delivery.py +npm ci --prefix scripts/wordpress-runtime --ignore-scripts +npm test --prefix scripts/wordpress-runtime +npm audit --prefix scripts/wordpress-runtime --audit-level=moderate +.\.venv\Scripts\python scripts/test_wordpress_runtime.py --pair 6.9.9 +.\.venv\Scripts\python scripts/test_wordpress_runtime.py --pair 7.1.2 pnpm --dir site build ``` diff --git a/brands/insonic/brand.json b/brands/insonic/brand.json new file mode 100644 index 0000000..782782e --- /dev/null +++ b/brands/insonic/brand.json @@ -0,0 +1,367 @@ +{ + "$schema": "../../skill/references/canon.schema.json", + "id": "insonic", + "kind": "sub-brand", + "title": "insonic", + "slug": "insonic", + "version": "1.0.0", + "canon": "2.0.0", + "wordmark_text": "insonic", + "affiliation": { + "ownership": "shruggietech-owned", + "showcase": "public", + "parent": "ShruggieTech", + "inheritance": "independent", + "endorsement": "shruggietech-project", + "service_credit": "none" + }, + "registry_base": "https://brand.shruggie.tech/insonic/brand", + "showcase_surface": "card", + "guidance": { + "surface_mode": "dark", + "written_form": "Write insonic in lowercase. Keep the product name distinct from file, speaker and assertion identifiers.", + "personality": [ + [ + "Calm", + "Uses quiet surfaces and concise explanations", + "Manufactured urgency" + ], + [ + "Precise", + "Names sources, speakers and processing states clearly", + "Unsupported accuracy claims" + ], + [ + "Connected", + "Shows relationships between voices and their source material", + "Anonymous decorative graph imagery" + ] + ], + "logo": "Three separated rounded voice forms establish a clear rhythm. Their staggered lengths and endpoints retain individuality; a strengthened Reduced master carries all three forms into tiny applications.", + "palette": "Glacial blue carries the identity on midnight surfaces; deep glacial blue carries it on light surfaces. State colors remain functional, with labels and symbols accompanying them.", + "sharp_edge": "A connected assertion retains its source and speaker context. Brand examples distinguish supplied audio, speaker references and generated audio without asserting recognition accuracy." + }, + "messaging": { + "slogan": { + "status": "approved", + "text": "Voice, with memory.", + "uses": [ + "visual-guide", + "strategy-reference", + "social-image" + ], + "source": "Owner, 2026-10-03: Everything is looking perfect. CONTINUE, in response to the shown fundamentals, social layout and three exact message-role questions.", + "approved_by": "repository owner", + "approved_on": "2026-10-03" + }, + "short_description": { + "status": "approved", + "text": "Voice archives, speaker models, and connected knowledge.", + "uses": [ + "site-metadata", + "visual-guide", + "consumer-data" + ], + "source": "Owner, 2026-10-03: Everything is looking perfect. CONTINUE, in response to the shown fundamentals, social layout and three exact message-role questions.", + "approved_by": "repository owner", + "approved_on": "2026-10-03" + }, + "long_description": { + "status": "approved", + "text": "insonic brings speaker-tagged audio into a growing archive of recordings, transcripts, and connected assertions. Its intended scope includes ingestion, storage, normalization, transcription, knowledge graph construction, speaker recognition, and voice replication, with speaker models improving as the collection grows.", + "uses": [ + "site-metadata", + "strategy-reference", + "consumer-data" + ], + "source": "Owner, 2026-10-03: Everything is looking perfect. CONTINUE, in response to the shown fundamentals, social layout and three exact message-role questions.", + "approved_by": "repository owner", + "approved_on": "2026-10-03" + } + }, + "accent": { + "bright": "#86BDF2", + "deep": "#256DA9", + "accessible": "#256DA9", + "dim": "#86BDF2" + }, + "semantic_colors": { + "emphasis": "#86BDF2", + "action": "#256DA9" + }, + "surfaces": { + "base": "#0C151B", + "card": "#14222C", + "popover": "#14222C", + "secondary": "#20323F", + "hover": "#2B4050" + }, + "light_surfaces": { + "base": "#F4F7F7", + "card": "#FFFFFF", + "popover": "#FFFFFF", + "secondary": "#E8EEF2", + "hover": "#E6EEF3", + "foreground": "#17282E", + "muted_foreground": "#50616B" + }, + "typography": { + "mode": "house", + "families": { + "display": { + "name": "Space Grotesk", + "weights": [ + 500, + 700 + ] + }, + "body": { + "name": "Geist", + "weights": [ + 400, + 500 + ] + }, + "mono": { + "name": "Geist Mono", + "weights": [ + 400 + ] + } + } + }, + "color_roles": { + "identity": [ + { + "id": "primary", + "label": "Glacial blue", + "source": "brand.accent.bright", + "use": "Mark and identity accent on dark surfaces" + }, + { + "id": "light-ink", + "label": "Deep glacial blue", + "source": "brand.accent.accessible", + "use": "Mark and readable identity accent on light surfaces" + } + ], + "combinations": [ + { + "id": "voice-forms", + "label": "Insonic A1", + "colors": [ + "primary" + ], + "artwork": "logo.full", + "use": "Three separated voice forms on dark surfaces" + } + ] + }, + "logo": { + "source_mode": "constructed", + "geometry_provenance": "glyphkit", + "grid": 1000, + "canvas_width": 1000, + "canvas_height": 1000, + "artwork_width": 585, + "artwork_height": 750, + "reduced_artwork_width": 620, + "reduced_artwork_height": 750, + "clear_space_units": 100, + "standalone_padding_units": 100, + "reduced_below_px": 32, + "standalone_mark_variant": "full", + "min_px": { + "mark": 16, + "horizontal": 160, + "stacked": 128, + "wordmark": 120 + }, + "application_icon": { + "background": "#0C151B" + }, + "role_colors": { + "color": { + "accent": "#86BDF2", + "wordmark": "#F2F7F6" + }, + "light": { + "accent": "#256DA9", + "wordmark": "#17282E" + }, + "black": { + "accent": "#000000", + "wordmark": "#000000" + }, + "white": { + "accent": "#FFFFFF", + "wordmark": "#FFFFFF" + } + }, + "construction": "Three separated rounded bars with staggered height and endpoints. Full stems are 125 units; Reduced stems are 160 units. Both retain three forms, as explicitly requested by the owner. No crossing bar. Generated directly with glyphkit rounded_rect and center_ink.", + "paths": { + "full": [ + { + "role": "accent", + "d": "M270 440 L270 440 C304.52 440 332.5 467.98 332.5 502.5 L332.5 597.5 C332.5 632.02 304.52 660 270 660 L270 660 C235.48 660 207.5 632.02 207.5 597.5 L207.5 502.5 C207.5 467.98 235.48 440 270 440 Z" + }, + { + "role": "accent", + "d": "M500 265 L500 265 C534.52 265 562.5 292.98 562.5 327.5 L562.5 672.5 C562.5 707.02 534.52 735 500 735 L500 735 C465.48 735 437.5 707.02 437.5 672.5 L437.5 327.5 C437.5 292.98 465.48 265 500 265 Z" + }, + { + "role": "accent", + "d": "M730 125 L730 125 C764.52 125 792.5 152.98 792.5 187.5 L792.5 812.5 C792.5 847.02 764.52 875 730 875 L730 875 C695.48 875 667.5 847.02 667.5 812.5 L667.5 187.5 C667.5 152.98 695.48 125 730 125 Z" + } + ], + "reduced": [ + { + "role": "accent", + "d": "M270 440 L270 440 C314.18 440 350 475.82 350 520 L350 580 C350 624.18 314.18 660 270 660 L270 660 C225.82 660 190 624.18 190 580 L190 520 C190 475.82 225.82 440 270 440 Z" + }, + { + "role": "accent", + "d": "M500 265 L500 265 C544.18 265 580 300.82 580 345 L580 655 C580 699.18 544.18 735 500 735 L500 735 C455.82 735 420 699.18 420 655 L420 345 C420 300.82 455.82 265 500 265 Z" + }, + { + "role": "accent", + "d": "M730 125 L730 125 C774.18 125 810 160.82 810 205 L810 795 C810 839.18 774.18 875 730 875 L730 875 C685.82 875 650 839.18 650 795 L650 205 C650 160.82 685.82 125 730 125 Z" + } + ] + } + }, + "identity_continuity": { + "record": "identity-continuity.json", + "status": "approved-canonical" + }, + "approval_ledger": { + "source_hashes": {}, + "gate_1": { + "status": "approved", + "approved_by": "repository owner", + "approved_on": "2026-10-03", + "scope": [ + "horizontal-lockup", + "reduced-and-platform", + "single-ink", + "stacked-lockup", + "wordmark-only" + ], + "derivative_config_sha256": "0ca187e955077d749bf23d61aef29d688d82df372c5a07cdc4ff9366e4b94db5", + "canonical_source_sha256": "4cf053159ef0fd109917ba741c091ce6eddef313d1c6b0e421227fb372b583e0" + }, + "gate_2": { + "status": "approved", + "approved_by": "repository owner", + "approved_on": "2026-10-03", + "derivative_manifest_sha256": "5c83f9a70847e9a7f4f9739bf9556db98d34f40a46e313050981118173ee94dd", + "surfaces": [ + "brand-landing-page", + "downloads", + "guideline-topics", + "public-metadata", + "registry-endpoints", + "showcase-card", + "social-preview", + "structured-data" + ] + } + }, + "social_copy": { + "slogan": "Voice, with memory.", + "layout": "slogan-only", + "composition": "classic", + "description_lines": [], + "approval": { + "approved_by": "repository owner", + "approved_on": "2026-10-03", + "source": "Owner, 2026-10-03: Everything is looking perfect. CONTINUE, in response to the shown fundamentals, social layout and three exact message-role questions." + } + }, + "descriptor": "Voice archives, speaker models, and connected knowledge.", + "functional_descriptor": "Voice archives, speaker models, and connected knowledge.", + "description_lines": [], + "category": "Voice and knowledge infrastructure", + "audience": "Developers building voice and knowledge systems", + "role": "insonic brings speaker-tagged audio into a growing archive of recordings, transcripts, and connected assertions. Its intended scope includes ingestion, storage, normalization, transcription, knowledge graph construction, speaker recognition, and voice replication, with speaker models improving as the collection grows.", + "governing_principle": "Keep voices connected.", + "register": "precise-dry", + "shruggie_flourish": false, + "voice": { + "qualities": [ + "calm", + "clear", + "precise", + "technically literate" + ], + "lead_with": [ + "voice archives", + "speaker identity", + "source-linked knowledge" + ], + "avoid": [ + "unsubstantiated recognition accuracy", + "claims of implemented planned capabilities", + "invented testimonials" + ] + }, + "assumptions": [ + "The name insonic is lowercase, including at the start of a sentence.", + "Use Reduced at 32 pixels and below. Full at 16 pixels is included in the continuity matrix for review.", + "The public product description identifies intended scope. This brand slice does not implement or evaluate the processing engine.", + "Application examples use synthetic filenames, speakers, assertions and graph data." + ], + "functional_colors": { + "warning": { + "dark": "#F2BE6E", + "light": "#805000" + }, + "error": { + "dark": "#F39494", + "light": "#A03035" + }, + "success": { + "dark": "#77C9A8", + "light": "#17654B" + }, + "information": { + "dark": "#86BDF2", + "light": "#256DA9" + } + }, + "brand_narrative": { + "name": "Write insonic in lowercase. Keep the product name distinct from file, speaker and assertion identifiers.", + "personality": [ + [ + "Calm", + "Uses quiet surfaces and concise explanations", + "Manufactured urgency" + ], + [ + "Precise", + "Names sources, speakers and processing states clearly", + "Unsupported accuracy claims" + ], + [ + "Connected", + "Shows relationships between voices and their source material", + "Anonymous decorative graph imagery" + ] + ], + "promises": [ + "Keep voices connected to their sources.", + "Make relationships clear.", + "Give developers readable states and next actions." + ], + "logo": "Three separated rounded voice forms establish a clear rhythm. Their staggered lengths and endpoints retain individuality; a strengthened Reduced master carries all three forms into tiny applications.", + "palette": "Glacial blue carries the identity on midnight surfaces; deep glacial blue carries it on light surfaces. State colors remain functional, with labels and symbols accompanying them.", + "sharp_edge": "A connected assertion retains its source and speaker context. Brand examples distinguish supplied audio, speaker references and generated audio without asserting recognition accuracy." + }, + "social_image_approval": { + "approved_by": "repository owner", + "approved_on": "2026-10-03", + "source": "Owner, 2026-10-03: approved. CONTINUE. Final revised Gate 2 packet SHA-256 fc80f4ef0c21c88a9166a6044838b081b005d3aa8993402e6f32084c209d9acf.", + "svg_sha256": "563afab6729e12f70c55595b56f9c6a1a23b1a6c3fb46a03d414a01ce079b63f", + "png_sha256": "9d59c9fe29d394b56a739a1c9cfc9c58c7f7c2d39d5169ab6ef88483412e1999" + } +} diff --git a/brands/insonic/build/README.md b/brands/insonic/build/README.md new file mode 100644 index 0000000..91fdf1f --- /dev/null +++ b/brands/insonic/build/README.md @@ -0,0 +1,11 @@ +# insonic Build Notes + +The constructed logo helper preserves the approved A1 Glacial blue Full and Reduced geometry. All generated derivatives must pass the source-bound identity continuity checks. + +Run `python brands/insonic/build/run_paths.py` from the repository to regenerate the approved path arrays. The launcher discovers the repository's BrandBuilder library independently of the working directory. In a standalone kit, run `python build/run_paths.py --templates /path/to/brandbuilder/templates` with an installed BrandBuilder library. `mk_paths.py` is the immutable, source-bound construction artifact; use the launcher to supply its library dependency. Validate the module with `python /path/to/brandbuilder/templates/validate_glyph.py build/mk_paths.py --module`. + +BrandBuilder 3.0.1 fixes `skill/templates/build_specimen.py` upstream to write its outlined SVG as UTF-8 without BOM and with LF line endings on Windows. The fix uses `open(..., newline="\n")` to retain Python 3.8 compatibility. It changes text serialization only; font outlines, logo geometry and social composition remain unchanged. + +Owner review also identified that the specimen placed the mark above the display name. The generator now measures the mark and font outline bounds, aligns their vertical ink centers in the header, and places the specimen label above that row. Approved logo path data and generated logo derivatives remain unchanged. + +BrandBuilder 3.0.1 also isolates screenshot temporary files per QC run, preventing concurrent kit builds from overwriting each other's desktop and mobile captures. diff --git a/brands/insonic/build/mk_paths.py b/brands/insonic/build/mk_paths.py new file mode 100644 index 0000000..3af492a --- /dev/null +++ b/brands/insonic/build/mk_paths.py @@ -0,0 +1,31 @@ +"""Insonic A1: three separated, rounded voice forms on a 1000-unit grid.""" + +import json +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent / 'templates')) +import glyphkit as G + +GRID = 1000 # Square construction canvas. +FULL_STEM = 125 # Width and endcap diameter of the selected Full voice forms. +REDUCED_STEM = 160 # Strengthened width and endcap diameter for tiny applications. +BARS = ((282.5, 440, 220), (512.5, 265, 470), (742.5, 125, 750)) # Center x, top y, height. +CLEAR_SPACE = 100 # Minimum space around the visible artwork in grid units. +REDUCED_BELOW_PX = 32 # Reduced master takes over at this size and below. + +# Keep the selected Full geometry exactly. The owner requested the same three +# forms with strengthened stems for the Reduced master on 2026-10-03. +full = G.center_ink([ + {'role': 'accent', + 'd': G.rounded_rect(cx - FULL_STEM / 2, y, FULL_STEM, height, FULL_STEM / 2)} + for cx, y, height in BARS +], GRID) +reduced = G.center_ink([ + {'role': 'accent', + 'd': G.rounded_rect(cx - REDUCED_STEM / 2, y, REDUCED_STEM, height, REDUCED_STEM / 2)} + for cx, y, height in BARS +], GRID) + +if __name__ == '__main__': + print(json.dumps({'grid': GRID, 'full': full, 'reduced': reduced}, indent=2)) diff --git a/brands/insonic/build/run_paths.py b/brands/insonic/build/run_paths.py new file mode 100644 index 0000000..b16e1fb --- /dev/null +++ b/brands/insonic/build/run_paths.py @@ -0,0 +1,27 @@ +"""Run the immutable approved helper with a resolved BrandBuilder library.""" + +import argparse +import runpy +import sys +from pathlib import Path + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--templates', type=Path, help='Installed BrandBuilder templates directory') + args = parser.parse_args() + here = Path(__file__).resolve().parent + library = args.templates + if library is None: + library = next((candidate for ancestor in here.parents + for candidate in (ancestor / 'skill' / 'templates', ancestor / 'templates') + if (candidate / 'glyphkit.py').is_file()), None) + if library is None or not (library / 'glyphkit.py').is_file(): + parser.error('Cannot find glyphkit.py. Pass --templates with the installed BrandBuilder templates directory.') + sys.path.insert(0, str(library.resolve())) + runpy.run_path(str(here / 'mk_paths.py'), run_name='__main__') + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/brands/insonic/identity-continuity.json b/brands/insonic/identity-continuity.json new file mode 100644 index 0000000..5ac4c36 --- /dev/null +++ b/brands/insonic/identity-continuity.json @@ -0,0 +1,1256 @@ +{ + "schema_version": 1, + "brand": "insonic", + "source_class": "glyphkit-constructed", + "recorded_on": "2026-10-03", + "source_revision": "codex/070-insonic-brand at 99245f87b4a1ceca8ecf35b0245dd0eb6916f4b4 plus private candidate source", + "source_files": [ + { + "path": "brand.json", + "purpose": "Candidate identity contract", + "bytes": 13122, + "sha256": "b679033bbf57a081e012e4a0de39adc2a369e18248827f5095a9ef84a721494f" + }, + { + "path": "build/mk_paths.py", + "purpose": "Glyphkit construction parameters", + "bytes": 1282, + "sha256": "c92e3bb3f8f55c80a4dd6662397f31e8819a7f7aeeaf6ca2ec7fba1171524daf" + } + ], + "identity_snapshot": { + "source_class": "glyphkit-constructed", + "geometry": { + "source_mode": "constructed", + "geometry_provenance": "glyphkit", + "paths": { + "full": [ + { + "role": "accent", + "d": "M270 440 L270 440 C304.52 440 332.5 467.98 332.5 502.5 L332.5 597.5 C332.5 632.02 304.52 660 270 660 L270 660 C235.48 660 207.5 632.02 207.5 597.5 L207.5 502.5 C207.5 467.98 235.48 440 270 440 Z" + }, + { + "role": "accent", + "d": "M500 265 L500 265 C534.52 265 562.5 292.98 562.5 327.5 L562.5 672.5 C562.5 707.02 534.52 735 500 735 L500 735 C465.48 735 437.5 707.02 437.5 672.5 L437.5 327.5 C437.5 292.98 465.48 265 500 265 Z" + }, + { + "role": "accent", + "d": "M730 125 L730 125 C764.52 125 792.5 152.98 792.5 187.5 L792.5 812.5 C792.5 847.02 764.52 875 730 875 L730 875 C695.48 875 667.5 847.02 667.5 812.5 L667.5 187.5 C667.5 152.98 695.48 125 730 125 Z" + } + ], + "reduced": [ + { + "role": "accent", + "d": "M270 440 L270 440 C314.18 440 350 475.82 350 520 L350 580 C350 624.18 314.18 660 270 660 L270 660 C225.82 660 190 624.18 190 580 L190 520 C190 475.82 225.82 440 270 440 Z" + }, + { + "role": "accent", + "d": "M500 265 L500 265 C544.18 265 580 300.82 580 345 L580 655 C580 699.18 544.18 735 500 735 L500 735 C455.82 735 420 699.18 420 655 L420 345 C420 300.82 455.82 265 500 265 Z" + }, + { + "role": "accent", + "d": "M730 125 L730 125 C774.18 125 810 160.82 810 205 L810 795 C810 839.18 774.18 875 730 875 L730 875 C685.82 875 650 839.18 650 795 L650 205 C650 160.82 685.82 125 730 125 Z" + } + ] + }, + "authoritative_input_ids": null + }, + "derivative_settings": { + "source_mode": "constructed", + "geometry_provenance": "glyphkit", + "grid": 1000, + "canvas_width": 1000, + "canvas_height": 1000, + "artwork_width": 585, + "artwork_height": 750, + "reduced_artwork_width": 620, + "reduced_artwork_height": 750, + "clear_space_units": 100, + "standalone_padding_units": 100, + "reduced_below_px": 32, + "standalone_mark_variant": "full", + "min_px": { + "mark": 16, + "horizontal": 160, + "stacked": 128, + "wordmark": 120 + }, + "application_icon": { + "background": "#0C151B" + }, + "role_colors": { + "color": { + "accent": "#86BDF2", + "wordmark": "#F2F7F6" + }, + "light": { + "accent": "#256DA9", + "wordmark": "#17282E" + }, + "black": { + "accent": "#000000", + "wordmark": "#000000" + }, + "white": { + "accent": "#FFFFFF", + "wordmark": "#FFFFFF" + } + }, + "construction": "Three separated rounded bars with staggered height and endpoints. Full stems are 125 units; Reduced stems are 160 units. Both retain three forms, as explicitly requested by the owner. No crossing bar. Generated directly with glyphkit rounded_rect and center_ink." + }, + "topology": { + "full": { + "path_count": 3, + "subpath_count": 3, + "closed_subpaths": 3, + "role_counts": { + "accent": 3 + }, + "command_counts": { + "C": 12, + "L": 12, + "M": 3, + "Z": 3 + }, + "fill_rules": { + "nonzero": 3 + } + }, + "reduced": { + "path_count": 3, + "subpath_count": 3, + "closed_subpaths": 3, + "role_counts": { + "accent": 3 + }, + "command_counts": { + "C": 12, + "L": 12, + "M": 3, + "Z": 3 + }, + "fill_rules": { + "nonzero": 3 + } + } + }, + "framing": { + "grid": 1000, + "canvas_width": 1000, + "canvas_height": 1000, + "artwork_width": 585, + "artwork_height": 750, + "reduced_artwork_width": 620, + "reduced_artwork_height": 750, + "clear_space_units": 100, + "standalone_padding_units": 100 + }, + "palette": { + "accent": { + "bright": "#86BDF2", + "deep": "#256DA9", + "accessible": "#256DA9", + "dim": "#86BDF2" + }, + "semantic_colors": { + "emphasis": "#86BDF2", + "action": "#256DA9" + }, + "surfaces": { + "base": "#0C151B", + "card": "#14222C", + "popover": "#14222C", + "secondary": "#20323F", + "hover": "#2B4050" + }, + "logo_role_colors": { + "color": { + "accent": "#86BDF2", + "wordmark": "#F2F7F6" + }, + "light": { + "accent": "#256DA9", + "wordmark": "#17282E" + }, + "black": { + "accent": "#000000", + "wordmark": "#000000" + }, + "white": { + "accent": "#FFFFFF", + "wordmark": "#FFFFFF" + } + } + }, + "typography": { + "wordmark_text": "insonic", + "typography": { + "mode": "house", + "families": { + "display": { + "name": "Space Grotesk", + "weights": [ + 500, + 700 + ] + }, + "body": { + "name": "Geist", + "weights": [ + 400, + 500 + ] + }, + "mono": { + "name": "Geist Mono", + "weights": [ + 400 + ] + } + } + } + }, + "sha256": "a9d5e28b5da1374feddb8a44e350432de65f50b2e076541e2cdeebb29c49fe24" + }, + "topology": { + "full": { + "path_count": 3, + "subpath_count": 3, + "closed_subpaths": 3, + "role_counts": { + "accent": 3 + }, + "command_counts": { + "C": 12, + "L": 12, + "M": 3, + "Z": 3 + }, + "fill_rules": { + "nonzero": 3 + } + }, + "reduced": { + "path_count": 3, + "subpath_count": 3, + "closed_subpaths": 3, + "role_counts": { + "accent": 3 + }, + "command_counts": { + "C": 12, + "L": 12, + "M": 3, + "Z": 3 + }, + "fill_rules": { + "nonzero": 3 + } + } + }, + "framing": { + "grid": 1000, + "canvas_width": 1000, + "canvas_height": 1000, + "artwork_width": 585, + "artwork_height": 750, + "reduced_artwork_width": 620, + "reduced_artwork_height": 750, + "clear_space_units": 100, + "standalone_padding_units": 100 + }, + "palette": { + "accent": { + "bright": "#86BDF2", + "deep": "#256DA9", + "accessible": "#256DA9", + "dim": "#86BDF2" + }, + "semantic_colors": { + "emphasis": "#86BDF2", + "action": "#256DA9" + }, + "surfaces": { + "base": "#0C151B", + "card": "#14222C", + "popover": "#14222C", + "secondary": "#20323F", + "hover": "#2B4050" + }, + "logo_role_colors": { + "color": { + "accent": "#86BDF2", + "wordmark": "#F2F7F6" + }, + "light": { + "accent": "#256DA9", + "wordmark": "#17282E" + }, + "black": { + "accent": "#000000", + "wordmark": "#000000" + }, + "white": { + "accent": "#FFFFFF", + "wordmark": "#FFFFFF" + } + } + }, + "renderer": { + "id": "node-resvg", + "version": "v24.11.0", + "settings_sha256": "b6d5b937dd71e1ac0b73f73495f171163413bfdb3d071503b63409b93d0d4393" + }, + "proofs": [ + { + "variant": "full", + "size_px": 256, + "surface": "dark", + "path": "proofs/full-256-dark.png", + "sha256": "024a439250dfbf996d11001dcf0d4459b5bcd21d552f5807d06da951bbc130e8", + "evidence": { + "side_by_side": { + "path": "evidence/full-256-dark/comparison-side-by-side.png", + "sha256": "1529d0553105d8fe593a027b04b4d495b0152eb67be8f758d9fd84c101aa6111" + }, + "overlay": { + "path": "evidence/full-256-dark/comparison-overlay.png", + "sha256": "024a439250dfbf996d11001dcf0d4459b5bcd21d552f5807d06da951bbc130e8" + }, + "silhouette_xor": { + "path": "evidence/full-256-dark/comparison-silhouette-xor.png", + "sha256": "aebc86755d7beb8b01bcd992ab44e1133afcb473511b7832eb82228be275473f" + }, + "color_difference": { + "path": "evidence/full-256-dark/comparison-color-difference.png", + "sha256": "42c7d7a412f704e9468d9ba58292b23e1d659ddb4e839345ad08bb37b44ee2e5" + } + } + }, + { + "variant": "full", + "size_px": 256, + "surface": "light", + "path": "proofs/full-256-light.png", + "sha256": "74760c720a3ac5ef8a5ff584a984824fabaedc764d570c1765c7f9a799de9ab5", + "evidence": { + "side_by_side": { + "path": "evidence/full-256-light/comparison-side-by-side.png", + "sha256": "262535dfab852834da40c186012dce4cf6890131a6b216a863a6958cfc926f83" + }, + "overlay": { + "path": "evidence/full-256-light/comparison-overlay.png", + "sha256": "74760c720a3ac5ef8a5ff584a984824fabaedc764d570c1765c7f9a799de9ab5" + }, + "silhouette_xor": { + "path": "evidence/full-256-light/comparison-silhouette-xor.png", + "sha256": "aebc86755d7beb8b01bcd992ab44e1133afcb473511b7832eb82228be275473f" + }, + "color_difference": { + "path": "evidence/full-256-light/comparison-color-difference.png", + "sha256": "42c7d7a412f704e9468d9ba58292b23e1d659ddb4e839345ad08bb37b44ee2e5" + } + } + }, + { + "variant": "full", + "size_px": 256, + "surface": "black", + "path": "proofs/full-256-black.png", + "sha256": "5c2d8eccb0538febae06767345d7c38b4b6e431bf0307867a8ce34f1180ca68d", + "evidence": { + "side_by_side": { + "path": "evidence/full-256-black/comparison-side-by-side.png", + "sha256": "123b31f4c8dbda5d54c8b21f3a26dab91eb0af1a20ac5938178bcd119c6d0758" + }, + "overlay": { + "path": "evidence/full-256-black/comparison-overlay.png", + "sha256": "5c2d8eccb0538febae06767345d7c38b4b6e431bf0307867a8ce34f1180ca68d" + }, + "silhouette_xor": { + "path": "evidence/full-256-black/comparison-silhouette-xor.png", + "sha256": "aebc86755d7beb8b01bcd992ab44e1133afcb473511b7832eb82228be275473f" + }, + "color_difference": { + "path": "evidence/full-256-black/comparison-color-difference.png", + "sha256": "42c7d7a412f704e9468d9ba58292b23e1d659ddb4e839345ad08bb37b44ee2e5" + } + } + }, + { + "variant": "full", + "size_px": 256, + "surface": "white", + "path": "proofs/full-256-white.png", + "sha256": "ae17f3bd932a1300a3ceef595a78e84b7091ba0fe87e185494700bf10dd12b07", + "evidence": { + "side_by_side": { + "path": "evidence/full-256-white/comparison-side-by-side.png", + "sha256": "c79baa54ddd98d10bcb7d924603fe7acbdd2fb368acd93f95a914cb5667f979d" + }, + "overlay": { + "path": "evidence/full-256-white/comparison-overlay.png", + "sha256": "ae17f3bd932a1300a3ceef595a78e84b7091ba0fe87e185494700bf10dd12b07" + }, + "silhouette_xor": { + "path": "evidence/full-256-white/comparison-silhouette-xor.png", + "sha256": "aebc86755d7beb8b01bcd992ab44e1133afcb473511b7832eb82228be275473f" + }, + "color_difference": { + "path": "evidence/full-256-white/comparison-color-difference.png", + "sha256": "42c7d7a412f704e9468d9ba58292b23e1d659ddb4e839345ad08bb37b44ee2e5" + } + } + }, + { + "variant": "full", + "size_px": 64, + "surface": "dark", + "path": "proofs/full-64-dark.png", + "sha256": "2b3f212c3c0560a6f094d666711e19ea20f7cabd134ef7f99c8a646de720043b", + "evidence": { + "side_by_side": { + "path": "evidence/full-64-dark/comparison-side-by-side.png", + "sha256": "b67b66747421856f1c6b094e90b686ba1f8dbaa20b06a9a1e162708c56179dc1" + }, + "overlay": { + "path": "evidence/full-64-dark/comparison-overlay.png", + "sha256": "2b3f212c3c0560a6f094d666711e19ea20f7cabd134ef7f99c8a646de720043b" + }, + "silhouette_xor": { + "path": "evidence/full-64-dark/comparison-silhouette-xor.png", + "sha256": "052c48aa8b5e4f2c40bcd0d1bb7a4b93276276285dfa3e8a971b435dc88fdfee" + }, + "color_difference": { + "path": "evidence/full-64-dark/comparison-color-difference.png", + "sha256": "e0a600268e4e838cbb2d645ad9b64530a4ccac386ae25907aad6d9f68271b733" + } + } + }, + { + "variant": "full", + "size_px": 64, + "surface": "light", + "path": "proofs/full-64-light.png", + "sha256": "5a670dec29d7413e8772499a3dd234125badb734f6545169f6d219d82f0c0511", + "evidence": { + "side_by_side": { + "path": "evidence/full-64-light/comparison-side-by-side.png", + "sha256": "35dba96d673343f96629bad6a815e40ca73b64212f55efb9d3c36d619d0ac9e1" + }, + "overlay": { + "path": "evidence/full-64-light/comparison-overlay.png", + "sha256": "5a670dec29d7413e8772499a3dd234125badb734f6545169f6d219d82f0c0511" + }, + "silhouette_xor": { + "path": "evidence/full-64-light/comparison-silhouette-xor.png", + "sha256": "052c48aa8b5e4f2c40bcd0d1bb7a4b93276276285dfa3e8a971b435dc88fdfee" + }, + "color_difference": { + "path": "evidence/full-64-light/comparison-color-difference.png", + "sha256": "e0a600268e4e838cbb2d645ad9b64530a4ccac386ae25907aad6d9f68271b733" + } + } + }, + { + "variant": "full", + "size_px": 64, + "surface": "black", + "path": "proofs/full-64-black.png", + "sha256": "416a6bfc38b760ddfaa4ccf2e409428a4ef7ba9be2a7bc6995b6d4230d1637d2", + "evidence": { + "side_by_side": { + "path": "evidence/full-64-black/comparison-side-by-side.png", + "sha256": "17195d3db1448a08a9ab68cd99e87afe72b656f4b37115c7fcb9bcf3b9951d85" + }, + "overlay": { + "path": "evidence/full-64-black/comparison-overlay.png", + "sha256": "416a6bfc38b760ddfaa4ccf2e409428a4ef7ba9be2a7bc6995b6d4230d1637d2" + }, + "silhouette_xor": { + "path": "evidence/full-64-black/comparison-silhouette-xor.png", + "sha256": "052c48aa8b5e4f2c40bcd0d1bb7a4b93276276285dfa3e8a971b435dc88fdfee" + }, + "color_difference": { + "path": "evidence/full-64-black/comparison-color-difference.png", + "sha256": "e0a600268e4e838cbb2d645ad9b64530a4ccac386ae25907aad6d9f68271b733" + } + } + }, + { + "variant": "full", + "size_px": 64, + "surface": "white", + "path": "proofs/full-64-white.png", + "sha256": "4714609f4ff8add025e4c349238b2326fbb19783bbd85f8f028244f09f415faf", + "evidence": { + "side_by_side": { + "path": "evidence/full-64-white/comparison-side-by-side.png", + "sha256": "7031126fa80e21ced4460a05e899c9b19499df4c496bc04e4fd6c0dbe951ec0a" + }, + "overlay": { + "path": "evidence/full-64-white/comparison-overlay.png", + "sha256": "4714609f4ff8add025e4c349238b2326fbb19783bbd85f8f028244f09f415faf" + }, + "silhouette_xor": { + "path": "evidence/full-64-white/comparison-silhouette-xor.png", + "sha256": "052c48aa8b5e4f2c40bcd0d1bb7a4b93276276285dfa3e8a971b435dc88fdfee" + }, + "color_difference": { + "path": "evidence/full-64-white/comparison-color-difference.png", + "sha256": "e0a600268e4e838cbb2d645ad9b64530a4ccac386ae25907aad6d9f68271b733" + } + } + }, + { + "variant": "full", + "size_px": 32, + "surface": "dark", + "path": "proofs/full-32-dark.png", + "sha256": "24c1fa9e496ae755f9c82998920de616774063ddfbe76eb4ab842498f7613956", + "evidence": { + "side_by_side": { + "path": "evidence/full-32-dark/comparison-side-by-side.png", + "sha256": "2ec912948e7b59d0173a09119c475b009f7630d57b758873ec3b91cd200b98bd" + }, + "overlay": { + "path": "evidence/full-32-dark/comparison-overlay.png", + "sha256": "24c1fa9e496ae755f9c82998920de616774063ddfbe76eb4ab842498f7613956" + }, + "silhouette_xor": { + "path": "evidence/full-32-dark/comparison-silhouette-xor.png", + "sha256": "b0775cba09eadf2fde241b35daa28cdcd03af6d23e99e0decd32b42f13f6d220" + }, + "color_difference": { + "path": "evidence/full-32-dark/comparison-color-difference.png", + "sha256": "30a5faf4033e50a90a2878943dfcf4cc66c8feff3fe92c17dc8c7e7a30e9e845" + } + } + }, + { + "variant": "full", + "size_px": 32, + "surface": "light", + "path": "proofs/full-32-light.png", + "sha256": "116109c4bf4622c8e032827012c4838c73859f2ba857c0501e25dcb9b2264457", + "evidence": { + "side_by_side": { + "path": "evidence/full-32-light/comparison-side-by-side.png", + "sha256": "adbcdb550516ae9cb16274293ac78a6ab8756615acb57ad400b37ce19bc4ae37" + }, + "overlay": { + "path": "evidence/full-32-light/comparison-overlay.png", + "sha256": "116109c4bf4622c8e032827012c4838c73859f2ba857c0501e25dcb9b2264457" + }, + "silhouette_xor": { + "path": "evidence/full-32-light/comparison-silhouette-xor.png", + "sha256": "b0775cba09eadf2fde241b35daa28cdcd03af6d23e99e0decd32b42f13f6d220" + }, + "color_difference": { + "path": "evidence/full-32-light/comparison-color-difference.png", + "sha256": "30a5faf4033e50a90a2878943dfcf4cc66c8feff3fe92c17dc8c7e7a30e9e845" + } + } + }, + { + "variant": "full", + "size_px": 32, + "surface": "black", + "path": "proofs/full-32-black.png", + "sha256": "179f2675c80a2cfc3c3d6ca2de0272b6c21e164d8c5aebcc9ed5d0b1fdca7808", + "evidence": { + "side_by_side": { + "path": "evidence/full-32-black/comparison-side-by-side.png", + "sha256": "360148046cd7fdecb33184e9a46965bd25743ea0f898243ba4611dcb0c0b9af4" + }, + "overlay": { + "path": "evidence/full-32-black/comparison-overlay.png", + "sha256": "179f2675c80a2cfc3c3d6ca2de0272b6c21e164d8c5aebcc9ed5d0b1fdca7808" + }, + "silhouette_xor": { + "path": "evidence/full-32-black/comparison-silhouette-xor.png", + "sha256": "b0775cba09eadf2fde241b35daa28cdcd03af6d23e99e0decd32b42f13f6d220" + }, + "color_difference": { + "path": "evidence/full-32-black/comparison-color-difference.png", + "sha256": "30a5faf4033e50a90a2878943dfcf4cc66c8feff3fe92c17dc8c7e7a30e9e845" + } + } + }, + { + "variant": "full", + "size_px": 32, + "surface": "white", + "path": "proofs/full-32-white.png", + "sha256": "e6c1da570b941e6e85791c7f2f2924c4adef31373f3dcf81966061ba3ea21bcf", + "evidence": { + "side_by_side": { + "path": "evidence/full-32-white/comparison-side-by-side.png", + "sha256": "13b60d7a97b4d5f6d5a464cabdd2041660e145c47e6e89b3c2d1c1b852d84384" + }, + "overlay": { + "path": "evidence/full-32-white/comparison-overlay.png", + "sha256": "e6c1da570b941e6e85791c7f2f2924c4adef31373f3dcf81966061ba3ea21bcf" + }, + "silhouette_xor": { + "path": "evidence/full-32-white/comparison-silhouette-xor.png", + "sha256": "b0775cba09eadf2fde241b35daa28cdcd03af6d23e99e0decd32b42f13f6d220" + }, + "color_difference": { + "path": "evidence/full-32-white/comparison-color-difference.png", + "sha256": "30a5faf4033e50a90a2878943dfcf4cc66c8feff3fe92c17dc8c7e7a30e9e845" + } + } + }, + { + "variant": "full", + "size_px": 16, + "surface": "dark", + "path": "proofs/full-16-dark.png", + "sha256": "704cb6c257d96afdc234342264607b2179f4328d2abf302ccd555777f54a3186", + "evidence": { + "side_by_side": { + "path": "evidence/full-16-dark/comparison-side-by-side.png", + "sha256": "6807f685c786b1ca885ecb31df7b04769110bc4562e293b131334758fe603fa9" + }, + "overlay": { + "path": "evidence/full-16-dark/comparison-overlay.png", + "sha256": "704cb6c257d96afdc234342264607b2179f4328d2abf302ccd555777f54a3186" + }, + "silhouette_xor": { + "path": "evidence/full-16-dark/comparison-silhouette-xor.png", + "sha256": "232c2eac760eae604f9dd43352c0b272396b7cfaf220081b54583b6d0cbc2320" + }, + "color_difference": { + "path": "evidence/full-16-dark/comparison-color-difference.png", + "sha256": "52dfe43025076f8a3b6d08d341916b9e38c0ecf7f4e6c75f0097e9ead901da27" + } + } + }, + { + "variant": "full", + "size_px": 16, + "surface": "light", + "path": "proofs/full-16-light.png", + "sha256": "ee13e1f08c59a27b1dd7e987d43959930219edc4b246439a86251a22cd07a6f6", + "evidence": { + "side_by_side": { + "path": "evidence/full-16-light/comparison-side-by-side.png", + "sha256": "1c52e99fcda86db0d3c25476015e7bd1c2191ac504522948cb15f544d7cdc4d2" + }, + "overlay": { + "path": "evidence/full-16-light/comparison-overlay.png", + "sha256": "ee13e1f08c59a27b1dd7e987d43959930219edc4b246439a86251a22cd07a6f6" + }, + "silhouette_xor": { + "path": "evidence/full-16-light/comparison-silhouette-xor.png", + "sha256": "232c2eac760eae604f9dd43352c0b272396b7cfaf220081b54583b6d0cbc2320" + }, + "color_difference": { + "path": "evidence/full-16-light/comparison-color-difference.png", + "sha256": "52dfe43025076f8a3b6d08d341916b9e38c0ecf7f4e6c75f0097e9ead901da27" + } + } + }, + { + "variant": "full", + "size_px": 16, + "surface": "black", + "path": "proofs/full-16-black.png", + "sha256": "354944c7fdf20a721b4cdce5f663ead137ccd46ec9cf924a180e8250c7506202", + "evidence": { + "side_by_side": { + "path": "evidence/full-16-black/comparison-side-by-side.png", + "sha256": "65c3c457c2153aaa98f28fcac61ce567e273e93181d4c52133c9ac4311890d9f" + }, + "overlay": { + "path": "evidence/full-16-black/comparison-overlay.png", + "sha256": "354944c7fdf20a721b4cdce5f663ead137ccd46ec9cf924a180e8250c7506202" + }, + "silhouette_xor": { + "path": "evidence/full-16-black/comparison-silhouette-xor.png", + "sha256": "232c2eac760eae604f9dd43352c0b272396b7cfaf220081b54583b6d0cbc2320" + }, + "color_difference": { + "path": "evidence/full-16-black/comparison-color-difference.png", + "sha256": "52dfe43025076f8a3b6d08d341916b9e38c0ecf7f4e6c75f0097e9ead901da27" + } + } + }, + { + "variant": "full", + "size_px": 16, + "surface": "white", + "path": "proofs/full-16-white.png", + "sha256": "a1b5e57f71c05cb653aa7c1e22ff41f0cc9746cf4aa743c9f9a87a044f03cfaf", + "evidence": { + "side_by_side": { + "path": "evidence/full-16-white/comparison-side-by-side.png", + "sha256": "2e24660a7237175b1730642c3380f7cbcabcf715f7a1b858f8bec40255389522" + }, + "overlay": { + "path": "evidence/full-16-white/comparison-overlay.png", + "sha256": "a1b5e57f71c05cb653aa7c1e22ff41f0cc9746cf4aa743c9f9a87a044f03cfaf" + }, + "silhouette_xor": { + "path": "evidence/full-16-white/comparison-silhouette-xor.png", + "sha256": "232c2eac760eae604f9dd43352c0b272396b7cfaf220081b54583b6d0cbc2320" + }, + "color_difference": { + "path": "evidence/full-16-white/comparison-color-difference.png", + "sha256": "52dfe43025076f8a3b6d08d341916b9e38c0ecf7f4e6c75f0097e9ead901da27" + } + } + }, + { + "variant": "reduced", + "size_px": 256, + "surface": "dark", + "path": "proofs/reduced-256-dark.png", + "sha256": "03605f49bf333b41262bf8fb930b095b429a92b570440693789ce6ad1c8a672e", + "evidence": { + "side_by_side": { + "path": "evidence/reduced-256-dark/comparison-side-by-side.png", + "sha256": "1fd7d384858901530473355cb6a2df62d3433c91ee9c7cd72f89aa612fb5bed1" + }, + "overlay": { + "path": "evidence/reduced-256-dark/comparison-overlay.png", + "sha256": "03605f49bf333b41262bf8fb930b095b429a92b570440693789ce6ad1c8a672e" + }, + "silhouette_xor": { + "path": "evidence/reduced-256-dark/comparison-silhouette-xor.png", + "sha256": "aebc86755d7beb8b01bcd992ab44e1133afcb473511b7832eb82228be275473f" + }, + "color_difference": { + "path": "evidence/reduced-256-dark/comparison-color-difference.png", + "sha256": "42c7d7a412f704e9468d9ba58292b23e1d659ddb4e839345ad08bb37b44ee2e5" + } + } + }, + { + "variant": "reduced", + "size_px": 256, + "surface": "light", + "path": "proofs/reduced-256-light.png", + "sha256": "74e4dc8f409b9565c5862059acca5e5e11b0d466ac2efa47132264afd49e27a0", + "evidence": { + "side_by_side": { + "path": "evidence/reduced-256-light/comparison-side-by-side.png", + "sha256": "fdf0070883ba2724d6a8daac6ac4cd1febb34f53a7e3f9faf0b87c253809ea2a" + }, + "overlay": { + "path": "evidence/reduced-256-light/comparison-overlay.png", + "sha256": "74e4dc8f409b9565c5862059acca5e5e11b0d466ac2efa47132264afd49e27a0" + }, + "silhouette_xor": { + "path": "evidence/reduced-256-light/comparison-silhouette-xor.png", + "sha256": "aebc86755d7beb8b01bcd992ab44e1133afcb473511b7832eb82228be275473f" + }, + "color_difference": { + "path": "evidence/reduced-256-light/comparison-color-difference.png", + "sha256": "42c7d7a412f704e9468d9ba58292b23e1d659ddb4e839345ad08bb37b44ee2e5" + } + } + }, + { + "variant": "reduced", + "size_px": 256, + "surface": "black", + "path": "proofs/reduced-256-black.png", + "sha256": "2bec9e881b430e91b22d8a1a3f7ca629266c3af0151849e3033e74a633a93792", + "evidence": { + "side_by_side": { + "path": "evidence/reduced-256-black/comparison-side-by-side.png", + "sha256": "414edf61fce289fa59f4be25df6dc7816c32c11f11d5480a7f2417a97175593d" + }, + "overlay": { + "path": "evidence/reduced-256-black/comparison-overlay.png", + "sha256": "2bec9e881b430e91b22d8a1a3f7ca629266c3af0151849e3033e74a633a93792" + }, + "silhouette_xor": { + "path": "evidence/reduced-256-black/comparison-silhouette-xor.png", + "sha256": "aebc86755d7beb8b01bcd992ab44e1133afcb473511b7832eb82228be275473f" + }, + "color_difference": { + "path": "evidence/reduced-256-black/comparison-color-difference.png", + "sha256": "42c7d7a412f704e9468d9ba58292b23e1d659ddb4e839345ad08bb37b44ee2e5" + } + } + }, + { + "variant": "reduced", + "size_px": 256, + "surface": "white", + "path": "proofs/reduced-256-white.png", + "sha256": "e684795709bdf38c751aca87a2e24462c7e0b61b70b4fa6b59d485486dd5e3f3", + "evidence": { + "side_by_side": { + "path": "evidence/reduced-256-white/comparison-side-by-side.png", + "sha256": "4e5c8819933ce531c00ab1dd23ad24059d8e6a4e2578a866f3ecdc5d74f5c8c7" + }, + "overlay": { + "path": "evidence/reduced-256-white/comparison-overlay.png", + "sha256": "e684795709bdf38c751aca87a2e24462c7e0b61b70b4fa6b59d485486dd5e3f3" + }, + "silhouette_xor": { + "path": "evidence/reduced-256-white/comparison-silhouette-xor.png", + "sha256": "aebc86755d7beb8b01bcd992ab44e1133afcb473511b7832eb82228be275473f" + }, + "color_difference": { + "path": "evidence/reduced-256-white/comparison-color-difference.png", + "sha256": "42c7d7a412f704e9468d9ba58292b23e1d659ddb4e839345ad08bb37b44ee2e5" + } + } + }, + { + "variant": "reduced", + "size_px": 64, + "surface": "dark", + "path": "proofs/reduced-64-dark.png", + "sha256": "8ee7398c63044fbb99e41e1ac1c8b52c7aba42bd43eca84183659b4044ec4874", + "evidence": { + "side_by_side": { + "path": "evidence/reduced-64-dark/comparison-side-by-side.png", + "sha256": "b10079c9a18c4c31473e6696f63f130adcf5d3dd23c3b44770e6cf342e6a17a0" + }, + "overlay": { + "path": "evidence/reduced-64-dark/comparison-overlay.png", + "sha256": "8ee7398c63044fbb99e41e1ac1c8b52c7aba42bd43eca84183659b4044ec4874" + }, + "silhouette_xor": { + "path": "evidence/reduced-64-dark/comparison-silhouette-xor.png", + "sha256": "052c48aa8b5e4f2c40bcd0d1bb7a4b93276276285dfa3e8a971b435dc88fdfee" + }, + "color_difference": { + "path": "evidence/reduced-64-dark/comparison-color-difference.png", + "sha256": "e0a600268e4e838cbb2d645ad9b64530a4ccac386ae25907aad6d9f68271b733" + } + } + }, + { + "variant": "reduced", + "size_px": 64, + "surface": "light", + "path": "proofs/reduced-64-light.png", + "sha256": "20eaa73a652daaa4421e452307642273dfb8dee8698b31a7f30446c12209257f", + "evidence": { + "side_by_side": { + "path": "evidence/reduced-64-light/comparison-side-by-side.png", + "sha256": "f4d66b5c71fb4a7bb36dcdb10150378bc44c3f5d62e35fab0d5f7e45a1ccfa31" + }, + "overlay": { + "path": "evidence/reduced-64-light/comparison-overlay.png", + "sha256": "20eaa73a652daaa4421e452307642273dfb8dee8698b31a7f30446c12209257f" + }, + "silhouette_xor": { + "path": "evidence/reduced-64-light/comparison-silhouette-xor.png", + "sha256": "052c48aa8b5e4f2c40bcd0d1bb7a4b93276276285dfa3e8a971b435dc88fdfee" + }, + "color_difference": { + "path": "evidence/reduced-64-light/comparison-color-difference.png", + "sha256": "e0a600268e4e838cbb2d645ad9b64530a4ccac386ae25907aad6d9f68271b733" + } + } + }, + { + "variant": "reduced", + "size_px": 64, + "surface": "black", + "path": "proofs/reduced-64-black.png", + "sha256": "9a3cb94630df47b0d6c128e5f3eb6f70f8031066025aa1f07b45197115629e0d", + "evidence": { + "side_by_side": { + "path": "evidence/reduced-64-black/comparison-side-by-side.png", + "sha256": "827b3736d74c4d399464bb86c7c80d242fcb7ab1392cc9c43f9aeebb22b3382b" + }, + "overlay": { + "path": "evidence/reduced-64-black/comparison-overlay.png", + "sha256": "9a3cb94630df47b0d6c128e5f3eb6f70f8031066025aa1f07b45197115629e0d" + }, + "silhouette_xor": { + "path": "evidence/reduced-64-black/comparison-silhouette-xor.png", + "sha256": "052c48aa8b5e4f2c40bcd0d1bb7a4b93276276285dfa3e8a971b435dc88fdfee" + }, + "color_difference": { + "path": "evidence/reduced-64-black/comparison-color-difference.png", + "sha256": "e0a600268e4e838cbb2d645ad9b64530a4ccac386ae25907aad6d9f68271b733" + } + } + }, + { + "variant": "reduced", + "size_px": 64, + "surface": "white", + "path": "proofs/reduced-64-white.png", + "sha256": "30cad10832beb22957b9f99d7e872be9162b07c18746267a9edddc32c500e099", + "evidence": { + "side_by_side": { + "path": "evidence/reduced-64-white/comparison-side-by-side.png", + "sha256": "fd0115fe4422c2e06c164767fe2561d181347fe7f43048ac8882e6d0d436a9c8" + }, + "overlay": { + "path": "evidence/reduced-64-white/comparison-overlay.png", + "sha256": "30cad10832beb22957b9f99d7e872be9162b07c18746267a9edddc32c500e099" + }, + "silhouette_xor": { + "path": "evidence/reduced-64-white/comparison-silhouette-xor.png", + "sha256": "052c48aa8b5e4f2c40bcd0d1bb7a4b93276276285dfa3e8a971b435dc88fdfee" + }, + "color_difference": { + "path": "evidence/reduced-64-white/comparison-color-difference.png", + "sha256": "e0a600268e4e838cbb2d645ad9b64530a4ccac386ae25907aad6d9f68271b733" + } + } + }, + { + "variant": "reduced", + "size_px": 32, + "surface": "dark", + "path": "proofs/reduced-32-dark.png", + "sha256": "c7bf39dbc0d89d025ad2273df16b0587c2e09a0dd9ad4fac35047332238c78cf", + "evidence": { + "side_by_side": { + "path": "evidence/reduced-32-dark/comparison-side-by-side.png", + "sha256": "a68ce8cd9a023e5c43601751582361e4a7637a303e4467b994d03f481ebf47fb" + }, + "overlay": { + "path": "evidence/reduced-32-dark/comparison-overlay.png", + "sha256": "c7bf39dbc0d89d025ad2273df16b0587c2e09a0dd9ad4fac35047332238c78cf" + }, + "silhouette_xor": { + "path": "evidence/reduced-32-dark/comparison-silhouette-xor.png", + "sha256": "b0775cba09eadf2fde241b35daa28cdcd03af6d23e99e0decd32b42f13f6d220" + }, + "color_difference": { + "path": "evidence/reduced-32-dark/comparison-color-difference.png", + "sha256": "30a5faf4033e50a90a2878943dfcf4cc66c8feff3fe92c17dc8c7e7a30e9e845" + } + } + }, + { + "variant": "reduced", + "size_px": 32, + "surface": "light", + "path": "proofs/reduced-32-light.png", + "sha256": "dd12149054863f97b5b69b7af015fd9e9caa8ca424e0f4560e139954aaffe18d", + "evidence": { + "side_by_side": { + "path": "evidence/reduced-32-light/comparison-side-by-side.png", + "sha256": "44ef1b33f7bf334a0ae3de3ac766f44cdc73de3c674a4a386d060dfc8f30dca1" + }, + "overlay": { + "path": "evidence/reduced-32-light/comparison-overlay.png", + "sha256": "dd12149054863f97b5b69b7af015fd9e9caa8ca424e0f4560e139954aaffe18d" + }, + "silhouette_xor": { + "path": "evidence/reduced-32-light/comparison-silhouette-xor.png", + "sha256": "b0775cba09eadf2fde241b35daa28cdcd03af6d23e99e0decd32b42f13f6d220" + }, + "color_difference": { + "path": "evidence/reduced-32-light/comparison-color-difference.png", + "sha256": "30a5faf4033e50a90a2878943dfcf4cc66c8feff3fe92c17dc8c7e7a30e9e845" + } + } + }, + { + "variant": "reduced", + "size_px": 32, + "surface": "black", + "path": "proofs/reduced-32-black.png", + "sha256": "c7ca22bebea3f6b3ec724cdcb08c00d7a9cee9bbd04ea57a2c7a60bffb34d28f", + "evidence": { + "side_by_side": { + "path": "evidence/reduced-32-black/comparison-side-by-side.png", + "sha256": "e7f15830992c4fee94c121fc34da459f21a7a2b42fe4283d89fba9cd747b81dd" + }, + "overlay": { + "path": "evidence/reduced-32-black/comparison-overlay.png", + "sha256": "c7ca22bebea3f6b3ec724cdcb08c00d7a9cee9bbd04ea57a2c7a60bffb34d28f" + }, + "silhouette_xor": { + "path": "evidence/reduced-32-black/comparison-silhouette-xor.png", + "sha256": "b0775cba09eadf2fde241b35daa28cdcd03af6d23e99e0decd32b42f13f6d220" + }, + "color_difference": { + "path": "evidence/reduced-32-black/comparison-color-difference.png", + "sha256": "30a5faf4033e50a90a2878943dfcf4cc66c8feff3fe92c17dc8c7e7a30e9e845" + } + } + }, + { + "variant": "reduced", + "size_px": 32, + "surface": "white", + "path": "proofs/reduced-32-white.png", + "sha256": "d4df5251e66b372b0d43acca8ac8796b728fb8760025cb6bdf6b62005a14e341", + "evidence": { + "side_by_side": { + "path": "evidence/reduced-32-white/comparison-side-by-side.png", + "sha256": "6a74908bb3b8f9d8be76d7768a5e526de0f588db49d3bcaf45f5eeda59322f7a" + }, + "overlay": { + "path": "evidence/reduced-32-white/comparison-overlay.png", + "sha256": "d4df5251e66b372b0d43acca8ac8796b728fb8760025cb6bdf6b62005a14e341" + }, + "silhouette_xor": { + "path": "evidence/reduced-32-white/comparison-silhouette-xor.png", + "sha256": "b0775cba09eadf2fde241b35daa28cdcd03af6d23e99e0decd32b42f13f6d220" + }, + "color_difference": { + "path": "evidence/reduced-32-white/comparison-color-difference.png", + "sha256": "30a5faf4033e50a90a2878943dfcf4cc66c8feff3fe92c17dc8c7e7a30e9e845" + } + } + }, + { + "variant": "reduced", + "size_px": 16, + "surface": "dark", + "path": "proofs/reduced-16-dark.png", + "sha256": "9adf8bf843d256b2d4d53b226ae68a78be9c85089401e2e97212cb6866b04417", + "evidence": { + "side_by_side": { + "path": "evidence/reduced-16-dark/comparison-side-by-side.png", + "sha256": "f58d691393ef3f6657a10e3cb80cd9a46ffbf8d948952fe587cb4d06b9ea1299" + }, + "overlay": { + "path": "evidence/reduced-16-dark/comparison-overlay.png", + "sha256": "9adf8bf843d256b2d4d53b226ae68a78be9c85089401e2e97212cb6866b04417" + }, + "silhouette_xor": { + "path": "evidence/reduced-16-dark/comparison-silhouette-xor.png", + "sha256": "232c2eac760eae604f9dd43352c0b272396b7cfaf220081b54583b6d0cbc2320" + }, + "color_difference": { + "path": "evidence/reduced-16-dark/comparison-color-difference.png", + "sha256": "52dfe43025076f8a3b6d08d341916b9e38c0ecf7f4e6c75f0097e9ead901da27" + } + } + }, + { + "variant": "reduced", + "size_px": 16, + "surface": "light", + "path": "proofs/reduced-16-light.png", + "sha256": "e6bfaacfb807242e789a53a68b08ce115a60d5b730d9aac81270ac7d931f514a", + "evidence": { + "side_by_side": { + "path": "evidence/reduced-16-light/comparison-side-by-side.png", + "sha256": "9b14da2e94b0bf083a0ad4d5cf2d0617d259454e5107fd514b523fe6d27e9fa3" + }, + "overlay": { + "path": "evidence/reduced-16-light/comparison-overlay.png", + "sha256": "e6bfaacfb807242e789a53a68b08ce115a60d5b730d9aac81270ac7d931f514a" + }, + "silhouette_xor": { + "path": "evidence/reduced-16-light/comparison-silhouette-xor.png", + "sha256": "232c2eac760eae604f9dd43352c0b272396b7cfaf220081b54583b6d0cbc2320" + }, + "color_difference": { + "path": "evidence/reduced-16-light/comparison-color-difference.png", + "sha256": "52dfe43025076f8a3b6d08d341916b9e38c0ecf7f4e6c75f0097e9ead901da27" + } + } + }, + { + "variant": "reduced", + "size_px": 16, + "surface": "black", + "path": "proofs/reduced-16-black.png", + "sha256": "282cf64c40afab5f8ac04e31b110d1ef4c58c827fd0a76e9fef9e2d9d3670f45", + "evidence": { + "side_by_side": { + "path": "evidence/reduced-16-black/comparison-side-by-side.png", + "sha256": "659e4e6401e92490600ae3e5ab780a14aa24644b51f68870b9541162ef9f1ae7" + }, + "overlay": { + "path": "evidence/reduced-16-black/comparison-overlay.png", + "sha256": "282cf64c40afab5f8ac04e31b110d1ef4c58c827fd0a76e9fef9e2d9d3670f45" + }, + "silhouette_xor": { + "path": "evidence/reduced-16-black/comparison-silhouette-xor.png", + "sha256": "232c2eac760eae604f9dd43352c0b272396b7cfaf220081b54583b6d0cbc2320" + }, + "color_difference": { + "path": "evidence/reduced-16-black/comparison-color-difference.png", + "sha256": "52dfe43025076f8a3b6d08d341916b9e38c0ecf7f4e6c75f0097e9ead901da27" + } + } + }, + { + "variant": "reduced", + "size_px": 16, + "surface": "white", + "path": "proofs/reduced-16-white.png", + "sha256": "719b2290b1ae5136cfb08f5dba78455c9835c6cb41deac848c7b0f7e12da4f46", + "evidence": { + "side_by_side": { + "path": "evidence/reduced-16-white/comparison-side-by-side.png", + "sha256": "c90ffb8c385979ab542829be7bf9454c39f5afe8edc1db5fdca6882d392893cd" + }, + "overlay": { + "path": "evidence/reduced-16-white/comparison-overlay.png", + "sha256": "719b2290b1ae5136cfb08f5dba78455c9835c6cb41deac848c7b0f7e12da4f46" + }, + "silhouette_xor": { + "path": "evidence/reduced-16-white/comparison-silhouette-xor.png", + "sha256": "232c2eac760eae604f9dd43352c0b272396b7cfaf220081b54583b6d0cbc2320" + }, + "color_difference": { + "path": "evidence/reduced-16-white/comparison-color-difference.png", + "sha256": "52dfe43025076f8a3b6d08d341916b9e38c0ecf7f4e6c75f0097e9ead901da27" + } + } + } + ], + "palette_qualification": { + "status": "passed", + "srgb_roles": { + "accent.accessible": "#256DA9", + "accent.bright": "#86BDF2", + "accent.deep": "#256DA9", + "accent.dim": "#86BDF2", + "logo_role_colors.black.accent": "#000000", + "logo_role_colors.black.wordmark": "#000000", + "logo_role_colors.color.accent": "#86BDF2", + "logo_role_colors.color.wordmark": "#F2F7F6", + "logo_role_colors.light.accent": "#256DA9", + "logo_role_colors.light.wordmark": "#17282E", + "logo_role_colors.white.accent": "#FFFFFF", + "logo_role_colors.white.wordmark": "#FFFFFF", + "semantic_colors.action": "#256DA9", + "semantic_colors.emphasis": "#86BDF2", + "surfaces.base": "#0C151B", + "surfaces.card": "#14222C", + "surfaces.hover": "#2B4050", + "surfaces.popover": "#14222C", + "surfaces.secondary": "#20323F" + }, + "oklch_roles": { + "accent.accessible": [ + 0.520405, + 0.117913, + 247.864235 + ], + "accent.bright": [ + 0.780193, + 0.095457, + 248.228882 + ], + "accent.deep": [ + 0.520405, + 0.117913, + 247.864235 + ], + "accent.dim": [ + 0.780193, + 0.095457, + 248.228882 + ], + "logo_role_colors.black.accent": [ + 0.0, + 0.0, + 0.0 + ], + "logo_role_colors.black.wordmark": [ + 0.0, + 0.0, + 0.0 + ], + "logo_role_colors.color.accent": [ + 0.780193, + 0.095457, + 248.228882 + ], + "logo_role_colors.color.wordmark": [ + 0.972085, + 0.00546, + 183.030767 + ], + "logo_role_colors.light.accent": [ + 0.520405, + 0.117913, + 247.864235 + ], + "logo_role_colors.light.wordmark": [ + 0.264692, + 0.025198, + 222.227519 + ], + "logo_role_colors.white.accent": [ + 1.0, + 0.0, + 0.0 + ], + "logo_role_colors.white.wordmark": [ + 1.0, + 0.0, + 0.0 + ], + "semantic_colors.action": [ + 0.520405, + 0.117913, + 247.864235 + ], + "semantic_colors.emphasis": [ + 0.780193, + 0.095457, + 248.228882 + ], + "surfaces.base": [ + 0.18985, + 0.018143, + 238.148271 + ], + "surfaces.card": [ + 0.244107, + 0.02721, + 240.363119 + ], + "surfaces.hover": [ + 0.36094, + 0.038454, + 241.941187 + ], + "surfaces.popover": [ + 0.244107, + 0.02721, + 240.363119 + ], + "surfaces.secondary": [ + 0.307528, + 0.03328, + 240.573893 + ] + }, + "checks": { + "color_vision": true, + "contrast": true, + "rendered_color": true, + "semantic_roles": true, + "single_ink": true, + "surfaces": true + }, + "evidence_sha256": "1d2879bc3c469859d3bd03a213dfb94a2b23669328b4672b8e62cc3b8cdc4f66" + }, + "status": "approved-canonical", + "historical_evidence": null, + "approval": { + "bundle_id": "insonic-a1-glacial-20261003", + "approved_by": "repository owner", + "approved_on": "2026-10-03", + "owner_wording": "Approved as provided", + "scope": [ + "full-master", + "reduced-master", + "palette", + "framing", + "topology", + "renderer", + "proof-matrix" + ], + "proposal_sha256": "a0a00d7abac8f5b5bc31fe7183bbdd6b48d4ec37e744271581a0771e963a6da5", + "source_snapshot_sha256": "a9d5e28b5da1374feddb8a44e350432de65f50b2e076541e2cdeebb29c49fe24" + }, + "record_sha256": "876ff6e7ef11ad5d78cd1145cfeaad1d7738b4581303354d9ff3b48a0f6886e8" +} diff --git a/scripts/audit_identity_continuity.py b/scripts/audit_identity_continuity.py index 32636ce..0148f32 100644 --- a/scripts/audit_identity_continuity.py +++ b/scripts/audit_identity_continuity.py @@ -28,12 +28,13 @@ "glitchpad": "legacy-constructed", "go-schedule": "legacy-constructed", "i-heart-pr-tours": "authoritative", + "insonic": "glyphkit-constructed", "local-companion": "glyphkit-constructed", "scruggs-tire-alignment": "authoritative", "shruggietech": "authoritative", } MIGRATION_BASELINE_BRANDS = set(BRAND_CLASSES) - { - "i-heart-pr-tours", "local-companion", "dancewithme865", "scruggs-tire-alignment", + "i-heart-pr-tours", "local-companion", "dancewithme865", "scruggs-tire-alignment", "insonic", } MIGRATION_BASELINE_REVISION = "011f35303ef1d555dbbbcf6708447cc321df40db" # S057 approved the current go-schedule mark after the historical migration. diff --git a/scripts/audit_publication_artifacts.py b/scripts/audit_publication_artifacts.py index 7355336..3e3dd89 100644 --- a/scripts/audit_publication_artifacts.py +++ b/scripts/audit_publication_artifacts.py @@ -27,6 +27,7 @@ "glitchpad", "go-schedule", "i-heart-pr-tours", + "insonic", "local-companion", "scruggs-tire-alignment", "shruggietech", diff --git a/scripts/release_contract.py b/scripts/release_contract.py index 1c85c87..dac23c1 100644 --- a/scripts/release_contract.py +++ b/scripts/release_contract.py @@ -137,6 +137,13 @@ "published 2.8.0 packages remain immutable recovery inputs; approved logo geometry and social " "compositions are unchanged." ), + "3.0.1": ( + "Existing kits need migration: **no required identity migration**. Regenerate and repin a kit only " + "to adopt the corrected outlined typography specimen and its Windows LF serialization. " + "insonic 1.0.0 is a new, separately approved brand kit. Existing approved logo geometry, " + "palette values, local font faces, social compositions and adapter APIs remain unchanged. " + "Nine owned brand archives are release-authorized; three client kits remain hosted candidates." + ), } diff --git a/scripts/test_brand_essentials_delivery.py b/scripts/test_brand_essentials_delivery.py index ea4cbbc..52545f5 100644 --- a/scripts/test_brand_essentials_delivery.py +++ b/scripts/test_brand_essentials_delivery.py @@ -14,7 +14,7 @@ def main(): sources = sorted((ROOT / "brands").glob("*/brand.json")) - assert len(sources) == 11, "production brand inventory changed" + assert len(sources) == 12, "production brand inventory changed" for source in sources: brand = json.loads(source.read_text(encoding="utf-8")) slug = brand["slug"] diff --git a/scripts/test_identity_continuity_audit.py b/scripts/test_identity_continuity_audit.py index c8bfbf0..fc2bf79 100644 --- a/scripts/test_identity_continuity_audit.py +++ b/scripts/test_identity_continuity_audit.py @@ -33,7 +33,8 @@ def test_inventory_is_complete_and_classification_is_explicit(self): self.assertEqual("authoritative", BRAND_CLASSES["scruggs-tire-alignment"]) self.assertEqual("authoritative", BRAND_CLASSES["dancewithme865"]) self.assertEqual("glyphkit-constructed", BRAND_CLASSES["local-companion"]) - self.assertTrue({"dancewithme865", "scruggs-tire-alignment", "local-companion"}.isdisjoint( + self.assertEqual("glyphkit-constructed", BRAND_CLASSES["insonic"]) + self.assertTrue({"dancewithme865", "scruggs-tire-alignment", "local-companion", "insonic"}.isdisjoint( MIGRATION_BASELINE_BRANDS)) def test_historical_record_is_revision_bound_without_fake_approval(self): diff --git a/scripts/test_insonic_construction.py b/scripts/test_insonic_construction.py new file mode 100644 index 0000000..c924571 --- /dev/null +++ b/scripts/test_insonic_construction.py @@ -0,0 +1,62 @@ +"""Exercise the approved insonic helper without changing its bound bytes.""" + +import hashlib +import json +import os +import shutil +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +TEMPLATES = ROOT / 'skill' / 'templates' +sys.path.insert(0, str(TEMPLATES)) +from process_utils import hidden_process_kwargs + + +class InsonicConstructionTests(unittest.TestCase): + def run_launcher(self, launcher, cwd, *arguments): + environment = dict(os.environ) + environment.pop('PYTHONPATH', None) + return subprocess.run([sys.executable, str(launcher)] + list(arguments), cwd=str(cwd), + env=environment, capture_output=True, text=True, encoding='utf-8', + **hidden_process_kwargs()) + + def assert_geometry(self, result): + self.assertEqual(result.returncode, 0, result.stderr) + actual = json.loads(result.stdout) + brand = json.loads((ROOT / 'brands/insonic/brand.json').read_text(encoding='utf-8')) + self.assertEqual(actual, {'grid': brand['logo']['grid'], **brand['logo']['paths']}) + + def test_repository_launcher_resolves_library_from_unrelated_working_directory(self): + with tempfile.TemporaryDirectory() as directory: + self.assert_geometry(self.run_launcher(ROOT / 'brands/insonic/build/run_paths.py', directory)) + + def test_portable_kit_launcher_accepts_the_installed_library_explicitly(self): + with tempfile.TemporaryDirectory() as directory: + build = Path(directory) / 'kit' / 'build' + build.mkdir(parents=True) + for name in ('mk_paths.py', 'run_paths.py'): + shutil.copyfile(ROOT / 'brands/insonic/build' / name, build / name) + self.assert_geometry(self.run_launcher(build / 'run_paths.py', directory, '--templates', str(TEMPLATES))) + + def test_missing_explicit_library_reports_actionable_error(self): + with tempfile.TemporaryDirectory() as directory: + result = self.run_launcher(ROOT / 'brands/insonic/build/run_paths.py', directory, + '--templates', directory) + self.assertEqual(result.returncode, 2) + self.assertIn('Pass --templates', result.stderr) + + def test_canonical_helper_keeps_its_approved_bytes_and_passes_module_validation(self): + helper = ROOT / 'brands/insonic/build/mk_paths.py' + continuity = json.loads((ROOT / 'brands/insonic/identity-continuity.json').read_text(encoding='utf-8')) + binding = next(item for item in continuity['source_files'] if item['path'] == 'build/mk_paths.py') + self.assertEqual(hashlib.sha256(helper.read_bytes()).hexdigest(), binding['sha256']) + result = self.run_launcher(TEMPLATES / 'validate_glyph.py', ROOT, str(helper), '--module') + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + +if __name__ == '__main__': + unittest.main() diff --git a/scripts/test_publication_workflow.py b/scripts/test_publication_workflow.py index 19e8c89..65510d1 100644 --- a/scripts/test_publication_workflow.py +++ b/scripts/test_publication_workflow.py @@ -27,6 +27,7 @@ "glitchpad", "go-schedule", "i-heart-pr-tours", + "insonic", "local-companion", "scruggs-tire-alignment", "shruggietech", @@ -393,6 +394,16 @@ def test_renderer_and_runtime_contract_are_exact(self): self.assertNotIn("rsvg-convert", text) self.assertNotIn("librsvg", text) + def test_wordpress_transport_and_audit_gates_precede_runtime_checks(self): + block = job_block(workflow_text(), "verified-build") + install = block.index("npm ci --prefix scripts/wordpress-runtime --ignore-scripts") + transport = block.index("npm test --prefix scripts/wordpress-runtime") + audit = block.index("npm audit --prefix scripts/wordpress-runtime --audit-level=moderate") + runtime = block.index("python scripts/test_wordpress_runtime.py --pair 6.9.9") + self.assertLess(install, transport) + self.assertLess(transport, audit) + self.assertLess(audit, runtime) + def test_verified_build_installs_both_playwright_browser_revisions(self): block = job_block(workflow_text(), "verified-build") self.assertIn("PLAYWRIGHT_SKIP_BROWSER_GC: \"1\"", block) diff --git a/scripts/test_release_contract.py b/scripts/test_release_contract.py index f01d044..58889f5 100644 --- a/scripts/test_release_contract.py +++ b/scripts/test_release_contract.py @@ -285,37 +285,38 @@ def test_production_logo_source_modes_and_identity_fingerprints_are_pinned(self) inputs[input_id]["mask_approved_by"], inputs[input_id]["mask_approved_on"])) - def test_repository_metadata_and_notes_agree_for_3_0_0(self): - metadata = release_contract.load_metadata(ROOT, "3.0.0") + def test_repository_metadata_and_notes_agree_for_3_0_1(self): + metadata = release_contract.load_metadata(ROOT, "3.0.1") notes = release_contract.render_notes(metadata) - self.assertEqual(metadata["skill_version"], "3.0.0") + self.assertEqual(metadata["skill_version"], "3.0.1") self.assertEqual(metadata["canon_version"], "2.0.0") - self.assertEqual(metadata["site_version"], "3.0.0") - self.assertEqual(release_contract.current_version(ROOT), "3.0.0") - self.assertIn("Skill version: `3.0.0`", notes) + self.assertEqual(metadata["site_version"], "3.0.1") + self.assertEqual(release_contract.current_version(ROOT), "3.0.1") + self.assertIn("Skill version: `3.0.1`", notes) self.assertIn("Canon version: `2.0.0`", notes) - self.assertIn("Existing kits need migration: **yes for the source and guide contract**", notes) - self.assertIn("distinct brand patch version", notes) + self.assertIn("Existing kits need migration: **no required identity migration**", notes) + self.assertIn("insonic 1.0.0", notes) self.assertIn("## Governed release impact", notes) self.assertIn("unchanged", notes) self.assertIn("WordPress adapter version: `1.0.0`", notes) self.assertNotIn("## [Unreleased]", notes) def test_expected_assets_are_exact_and_use_embedded_brand_versions(self): - metadata = release_contract.load_metadata(ROOT, "3.0.0") + metadata = release_contract.load_metadata(ROOT, "3.0.1") self.assertEqual(set(release_contract.expected_assets(metadata)), { - "shruggie-brandbuilder-3.0.0.skill", - "shruggie-brandbuilder-3.0.0-portable.zip", - "shruggietech-brand-1.1.1-bb3.0.0.zip", - "fragcap-brand-1.2.1-bb3.0.0.zip", - "go-schedule-brand-2.0.1-bb3.0.0.zip", - "glitchpad-brand-1.2.1-bb3.0.0.zip", - "covarity-brand-1.1.1-bb3.0.0.zip", - "eso-weave-brand-1.1.1-bb3.0.0.zip", - "cueson-brand-1.1.1-bb3.0.0.zip", - "local-companion-brand-1.0.1-bb3.0.0.zip", + "shruggie-brandbuilder-3.0.1.skill", + "shruggie-brandbuilder-3.0.1-portable.zip", + "shruggietech-brand-1.1.1-bb3.0.1.zip", + "fragcap-brand-1.2.1-bb3.0.1.zip", + "go-schedule-brand-2.0.1-bb3.0.1.zip", + "glitchpad-brand-1.2.1-bb3.0.1.zip", + "covarity-brand-1.1.1-bb3.0.1.zip", + "eso-weave-brand-1.1.1-bb3.0.1.zip", + "cueson-brand-1.1.1-bb3.0.1.zip", + "local-companion-brand-1.0.1-bb3.0.1.zip", + "insonic-brand-1.0.0-bb3.0.1.zip", }) self.assertEqual( {slug: values["version"] for slug, values in metadata["brands"].items()}, @@ -328,6 +329,7 @@ def test_expected_assets_are_exact_and_use_embedded_brand_versions(self): "eso-weave": "1.1.1", "cueson": "1.1.1", "local-companion": "1.0.1", + "insonic": "1.0.0", }, ) @@ -375,9 +377,9 @@ def read_with_stale_site(path): release_contract, "read_text", side_effect=read_with_stale_site ): with self.assertRaisesRegex( - ValueError, "site package version 1.1.2 does not match release 3.0.0" + ValueError, "site package version 1.1.2 does not match release 3.0.1" ): - release_contract.load_metadata(ROOT, "3.0.0") + release_contract.load_metadata(ROOT, "3.0.1") def test_wordpress_adapter_rejects_unsupported_older_canon(self): original_read_text = release_contract.read_text @@ -400,7 +402,7 @@ def read_with_supported_older_canon(path): release_contract, "read_text", side_effect=read_with_supported_older_canon ): with self.assertRaisesRegex(ValueError, "wordpress_adapter 1.0.0 is incompatible with brand_canon 1.2.0"): - release_contract.load_metadata(ROOT, "3.0.0") + release_contract.load_metadata(ROOT, "3.0.1") def test_archive_paths_reject_parent_traversal(self): with tempfile.TemporaryDirectory() as tmp: diff --git a/scripts/wordpress-runtime/fetch-client/README.md b/scripts/wordpress-runtime/fetch-client/README.md new file mode 100644 index 0000000..ed2056c --- /dev/null +++ b/scripts/wordpress-runtime/fetch-client/README.md @@ -0,0 +1,7 @@ +# WordPress fixture HTTP transport + +The pinned `@wordpress/env` 11.16.0 uses two HTTP-client entry points: `got(url).json()` for the WordPress stable-version response and `got.stream(url)` with a progress event for ZIP downloads. This fixture overrides its `got` dependency with a local, dependency-free compatibility transport using Node's built-in `fetch` and streams. It performs uncached requests and removes `got`, `cacheable-request` and `http-cache-semantics` from the installed dependency graph. This addresses the unpatched GHSA-ch52-4w7c-c8xp dependency without suppressing the required npm audit. + +The transport supports redirects, JSON, byte streams, progress, HTTP error propagation, truncated-stream errors, cancellation and a two-minute request timeout. It does not implement got's broader API. The fixture pins wp-env and tests its resolution to this transport; an upstream runner upgrade must review the compatibility surface. This is test infrastructure only and is not bundled into brand kits or consumer applications. + +Run `npm ci --ignore-scripts`, `npm test`, and `npm audit --audit-level=moderate` from `scripts/wordpress-runtime/`, then exercise both pinned WordPress/PHP pairs through `scripts/test_wordpress_runtime.py`. diff --git a/scripts/wordpress-runtime/fetch-client/index.cjs b/scripts/wordpress-runtime/fetch-client/index.cjs new file mode 100644 index 0000000..609ba55 --- /dev/null +++ b/scripts/wordpress-runtime/fetch-client/index.cjs @@ -0,0 +1,52 @@ +'use strict'; + +const { Readable } = require('node:stream'); + +// This fixture replaces got, rather than patching or retaining its cache chain. +// Keep this surface limited to the two APIs used by pinned @wordpress/env. +async function responseFor(url, signal) { + const response = await fetch(url, { + cache: 'no-store', + signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(120_000)]) : AbortSignal.timeout(120_000), + }); + if (!response.ok) { + await response.body?.cancel(); + throw new Error(`Download failed with HTTP ${response.status}: ${url}`); + } + return response; +} + +function request(url) { + return { + async json() { + return (await responseFor(url)).json(); + }, + }; +} + +request.stream = function stream(url) { + const controller = new AbortController(); + let output; + output = Readable.from((async function* download() { + const response = await responseFor(url, controller.signal); + const total = Number(response.headers.get('content-length')) || 0; + let received = 0; + const body = response.body; + if (body) { + for await (const chunk of body) { + received += chunk.length; + output.emit('downloadProgress', { percent: total ? Math.min(received / total, 1) : 0 }); + yield Buffer.from(chunk); + } + } + output.emit('downloadProgress', { percent: 1 }); + })(), { objectMode: false }); + const destroy = output.destroy; + output.destroy = function cancel(error) { + controller.abort(); + return destroy.call(this, error); + }; + return output; +}; + +module.exports = request; diff --git a/scripts/wordpress-runtime/fetch-client/package.json b/scripts/wordpress-runtime/fetch-client/package.json new file mode 100644 index 0000000..4e8ef52 --- /dev/null +++ b/scripts/wordpress-runtime/fetch-client/package.json @@ -0,0 +1,10 @@ +{ + "name": "@shruggietech/wp-env-fetch", + "version": "1.0.0", + "private": true, + "description": "Uncached built-in fetch transport for the two HTTP APIs used by pinned wp-env", + "main": "index.cjs", + "engines": { + "node": ">=20.3.0" + } +} diff --git a/scripts/wordpress-runtime/fetch-client/test.cjs b/scripts/wordpress-runtime/fetch-client/test.cjs new file mode 100644 index 0000000..6335436 --- /dev/null +++ b/scripts/wordpress-runtime/fetch-client/test.cjs @@ -0,0 +1,129 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { createServer } = require('node:http'); +const { once } = require('node:events'); +const { readFileSync, existsSync, mkdtempSync, rmSync } = require('node:fs'); +const { tmpdir } = require('node:os'); +const path = require('node:path'); +const { Writable } = require('node:stream'); +const { pipeline } = require('node:stream/promises'); +const { test } = require('node:test'); +const client = require('./index.cjs'); + +async function fixture(handler, check) { + const server = createServer(handler); + server.listen(0, '127.0.0.1'); + await once(server, 'listening'); + try { + await check(`http://127.0.0.1:${server.address().port}`); + } finally { + server.closeAllConnections(); + await new Promise(resolve => server.close(resolve)); + } +} + +test('each JSON request reaches the server and does not replay another response or cookie', async () => { + let requests = 0; + await fixture((request, response) => { + assert.equal(request.headers.cookie, undefined); + response.writeHead(200, { 'content-type': 'application/json', 'cache-control': 'public, max-age=3600', 'set-cookie': 'session=private' }); + response.end(JSON.stringify({ request: ++requests })); + }, async base => { + assert.deepEqual(await client(base).json(), { request: 1 }); + assert.deepEqual(await client(base).json(), { request: 2 }); + }); + assert.equal(requests, 2); +}); + +test('ZIP bytes and progress survive redirects and streaming pipeline backpressure', async () => { + const bytes = Buffer.from('504b03040000000000000000', 'hex'); + await fixture((request, response) => { + if (request.url === '/redirect') { + response.writeHead(302, { location: '/archive' }).end(); + } else { + response.writeHead(200, { 'content-length': bytes.length }); + response.end(bytes); + } + }, async base => { + const received = []; + const progress = []; + const input = client.stream(`${base}/redirect`); + input.on('downloadProgress', value => progress.push(value.percent)); + await pipeline(input, new Writable({ write(chunk, encoding, done) { received.push(chunk); setImmediate(done); } })); + assert.deepEqual(Buffer.concat(received), bytes); + assert.equal(progress.at(-1), 1); + }); +}); + +test('HTTP failures reject both APIs instead of writing error pages as ZIPs', async () => { + await fixture((request, response) => response.writeHead(404).end('missing'), async base => { + await assert.rejects(client(base).json(), /HTTP 404/); + await assert.rejects(pipeline(client.stream(base), new Writable({ write(chunk, encoding, done) { done(); } })), /HTTP 404/); + }); +}); + +test('a truncated response fails the download pipeline', async () => { + await fixture((request, response) => { + response.writeHead(200, { 'content-length': 1000 }); + response.flushHeaders(); + response.write('short'); + setImmediate(() => response.destroy()); + }, async base => { + await assert.rejects(pipeline(client.stream(base), new Writable({ write(chunk, encoding, done) { done(); } }))); + }); +}); + +test('destroying the download stream cancels its pending request', { timeout: 5000 }, async () => { + let acceptClose; + const connectionClosed = new Promise(resolve => { acceptClose = resolve; }); + await fixture((request, response) => { + response.once('close', acceptClose); + response.writeHead(200); + response.flushHeaders(); + response.write('first'); + }, async base => { + const input = client.stream(base); + const closed = once(input, 'close'); + await once(input, 'data'); + input.destroy(); + await closed; + await connectionClosed; + assert.equal(input.destroyed, true); + }); +}); + +test('pinned wp-env resolves the replacement and no vulnerable cache package remains', () => { + const installed = path.dirname(require.resolve('@wordpress/env/package.json')); + assert.equal(JSON.parse(readFileSync(path.join(installed, 'package.json'), 'utf8')).version, '11.16.0'); + const resolved = require.resolve('got', { paths: [installed] }); + assert.equal(require(resolved), client); + assert.equal(existsSync(path.join(__dirname, '..', 'node_modules', 'http-cache-semantics')), false); + assert.equal(existsSync(path.join(__dirname, '..', 'node_modules', 'cacheable-request')), false); +}); + +test('the installed wp-env downloads and extracts a ZIP through the replacement', async () => { + const installed = path.dirname(require.resolve('@wordpress/env/package.json')); + const { downloadZipSource } = require(path.join(installed, 'lib', 'download-sources.js')); + const AdmZip = require('adm-zip'); + const zip = new AdmZip(); + zip.addFile('source/README.txt', Buffer.from('fresh fixture bytes')); + const bytes = zip.toBuffer(); + const temporary = mkdtempSync(path.join(tmpdir(), 'stbb-fetch-')); + try { + await fixture((request, response) => response.writeHead(200, { 'content-length': bytes.length }).end(bytes), async base => { + const destination = path.join(temporary, 'source'); + const progress = []; + await downloadZipSource({ url: base, path: destination }, { + onProgress: value => progress.push(value), spinner: {}, debug: false, + }); + assert.equal(readFileSync(path.join(destination, 'README.txt'), 'utf8'), 'fresh fixture bytes'); + assert.equal(progress[0], 0); + assert.equal(progress.at(-1), 1); + assert.equal(existsSync(`${destination}.zip`), false); + }); + } finally { + assert.ok(path.resolve(temporary).startsWith(`${path.resolve(tmpdir())}${path.sep}`)); + rmSync(temporary, { recursive: true, force: true }); + } +}); diff --git a/scripts/wordpress-runtime/package-lock.json b/scripts/wordpress-runtime/package-lock.json index 891e2e4..322ae3b 100644 --- a/scripts/wordpress-runtime/package-lock.json +++ b/scripts/wordpress-runtime/package-lock.json @@ -8,7 +8,16 @@ "name": "shruggie-wordpress-runtime-fixture", "version": "1.0.0", "devDependencies": { - "@wordpress/env": "11.16.0" + "@wordpress/env": "11.16.0", + "got": "file:./fetch-client" + } + }, + "fetch-client": { + "name": "@shruggietech/wp-env-fetch", + "version": "1.0.0", + "dev": true, + "engines": { + "node": ">=20.3.0" } }, "node_modules/@inquirer/ansi": { @@ -696,6 +705,7 @@ "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -1409,32 +1419,6 @@ "@simple-git/args-pathspec": "^1.0.3" } }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dev": true, - "license": "MIT", - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@types/aws-lambda": { "version": "8.10.163", "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.163.tgz", @@ -1449,26 +1433,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/jsonwebtoken": { "version": "9.0.10", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", @@ -1480,16 +1444,6 @@ "@types/node": "*" } }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -1503,20 +1457,11 @@ "integrity": "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~8.9.0" } }, - "node_modules/@types/responselike": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@wordpress/env": { "version": "11.16.0", "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-11.16.0.tgz", @@ -2000,35 +1945,6 @@ "node": ">= 0.8" } }, - "node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/call-bind": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", @@ -2221,19 +2137,6 @@ "node": ">=0.8" } }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-response": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2381,16 +2284,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -2513,16 +2406,6 @@ "node": ">= 0.8" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2899,22 +2782,6 @@ "node": ">= 0.4" } }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", @@ -2951,30 +2818,8 @@ } }, "node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } + "resolved": "fetch-client", + "link": true }, "node_modules/graceful-fs": { "version": "4.2.11", @@ -3048,13 +2893,6 @@ "node": ">= 0.4" } }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "dev": true, - "license": "BSD-2-Clause" - }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -3076,20 +2914,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, "node_modules/iconv-lite": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", @@ -3307,13 +3131,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -3394,16 +3211,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -3534,16 +3341,6 @@ "node": ">=4" } }, - "node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -3637,16 +3434,6 @@ "node": ">=6" } }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/minimatch": { "version": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", @@ -3727,19 +3514,6 @@ "node": ">= 0.6" } }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -3858,16 +3632,6 @@ "dev": true, "license": "ISC" }, - "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -3990,17 +3754,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/qs": { "version": "6.16.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", @@ -4018,19 +3771,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -4107,26 +3847,6 @@ "node": ">=0.10.0" } }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lowercase-keys": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", diff --git a/scripts/wordpress-runtime/package.json b/scripts/wordpress-runtime/package.json index 74c04cd..ea7dabb 100644 --- a/scripts/wordpress-runtime/package.json +++ b/scripts/wordpress-runtime/package.json @@ -3,10 +3,15 @@ "private": true, "version": "1.0.0", "description": "Pinned native WordPress integration fixture for BrandBuilder", + "scripts": { + "test": "node --test fetch-client/test.cjs" + }, "devDependencies": { - "@wordpress/env": "11.16.0" + "@wordpress/env": "11.16.0", + "got": "file:./fetch-client" }, "overrides": { - "qs": "6.16.0" + "qs": "6.16.0", + "got": "$got" } } diff --git a/site/package.json b/site/package.json index ee8c68a..a15c8ec 100644 --- a/site/package.json +++ b/site/package.json @@ -1,6 +1,6 @@ { "name": "shruggie-brand-site", - "version": "3.0.0", + "version": "3.0.1", "private": true, "packageManager": "pnpm@10.28.2", "scripts": { diff --git a/site/tests/site.test.mjs b/site/tests/site.test.mjs index 216ae6a..bbdd1e1 100644 --- a/site/tests/site.test.mjs +++ b/site/tests/site.test.mjs @@ -98,7 +98,7 @@ export function interactionStyleProblems(styles) { if (interactionStyleProblems(globalStyles).length > 0) throw new Error(`interaction style contract failed: ${interactionStyleProblems(globalStyles).join(', ')}`); export const routeRecords = routeContract.routes; -const expectedBrandSlugs = ['covarity', 'cueson', 'dancewithme865', 'eso-weave', 'fragcap', 'glitchpad', 'go-schedule', 'i-heart-pr-tours', 'local-companion', 'scruggs-tire-alignment', 'shruggietech']; +const expectedBrandSlugs = ['covarity', 'cueson', 'dancewithme865', 'eso-weave', 'fragcap', 'glitchpad', 'go-schedule', 'i-heart-pr-tours', 'insonic', 'local-companion', 'scruggs-tire-alignment', 'shruggietech']; export function formalSourceHex(brand, reference) { const [root, ...path] = reference.split('.'); const semantic = brand.affiliation?.inheritance === 'shruggietech-house' diff --git a/skill/CHANGELOG.md b/skill/CHANGELOG.md index f6739c0..37d2844 100644 --- a/skill/CHANGELOG.md +++ b/skill/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +## 3.0.1 - 2026-10-03 + +- Added the separately approved insonic 1.0.0 owned-project brand, independent Glacial blue identity, strengthened three-form Reduced master and exact messages under S070. +- Aligned generated type specimen headers with measured mark and font ink bounds and enforced LF serialization on Windows. Existing approved logo geometry, fonts, social bytes and adapter contracts remain unchanged. +- Isolated temporary page screenshots per QC run to preserve each brand's captures during concurrent builds. +- Added an insonic construction launcher that supplies the installed glyphkit library without modifying the immutable approved helper. +- Regenerate and repin packages only to adopt the corrected specimen or new insonic kit. Nine owned brand archives join the skill and portable distribution; the site hosts twelve brands and retains existing client release eligibility. + ## 3.0.0 - 2026-09-29 - Advanced Brand Canon to 2.0.0 for explicit messaging roles and the `guide` to `guidance` source migration, and BrandBuilder to 3.0.0 for literal PDF and portable guide projections. All eleven affected brand records receive patch versions with unchanged identity geometry and approved social compositions. diff --git a/skill/SKILL.md b/skill/SKILL.md index 122b9f7..72f230b 100644 --- a/skill/SKILL.md +++ b/skill/SKILL.md @@ -4,7 +4,7 @@ description: Build or audit a complete brand kit for a ShruggieTech-owned or thi license: Apache-2.0. The code, templates and reference documentation are licensed under the Apache License 2.0. Apache-2.0 section 6 grants no trademark rights, and the ShruggieTech and sub-brand names, wordmarks and logo geometry are additionally reserved: see LICENSE-BRAND.md. Bundled fonts keep their own SIL Open Font License 1.1. compatibility: Python 3.8 or newer. `coloraide` is required for color work, fontTools is required for generated and fixed typography, Pillow is required when raster supplied inputs request palette evidence, Brotli lets fontTools inspect WOFF2 metadata, and jsonschema validates shadcn registry payloads. Render-only capabilities still degrade to named skips. Run `templates/probe.py` first and route off its report. metadata: - version: 3.0.0 + version: 3.0.1 canon: 2.0.0 interface-canon: 1.0.1 component-recipes: 1.1.0 diff --git a/skill/references/release-impact.json b/skill/references/release-impact.json index 1632a92..a2d7991 100644 --- a/skill/references/release-impact.json +++ b/skill/references/release-impact.json @@ -1,12 +1,12 @@ { "$schema": "release-impact.schema.json", "schema_version": 1, - "brandbuilder_version": "3.0.0", + "brandbuilder_version": "3.0.1", "identity_redesign": false, "surfaces": { "identity": { "classification": "unaffected", - "summary": "Approved Full and Reduced masters, source geometry, and identity proof bytes remain unchanged." + "summary": "Existing approved masters and proof bytes remain unchanged. insonic 1.0.0 is a new, separately approved three-form Glacial blue identity." }, "palette": { "classification": "unaffected", @@ -17,8 +17,8 @@ "summary": "Approved local font families, faces, and licenses remain unchanged." }, "platform_assets": { - "classification": "required", - "summary": "Regenerate PDF and portable guides from exact approved message roles, Brand essentials, and declared Usage limits. Approved logos, icons, and social compositions remain source-bound." + "classification": "optional", + "summary": "Regenerate only to adopt corrected type specimen header alignment, explicit LF serialization on Windows and isolated QC captures. Approved logos, icons and social compositions remain source-bound." }, "web_react": { "classification": "unaffected", @@ -34,11 +34,11 @@ }, "documentation": { "classification": "required", - "summary": "Hosted, portable, and PDF guides publish exact approved roles and source-bound Brand essentials. The first hosted guide page uses its canonical route; published manuals link the exact release. Gate 2 approved site descriptions remain site-only; unresolved legacy descriptions are omitted from portfolio cards and public brand records." + "summary": "Publish insonic guidelines, approved messages, registries and downloads through the standard generated routes. Existing approved message-role uses remain unchanged; the manual links the exact release." }, "recovery": { "classification": "required", - "summary": "Regenerate all eleven site kits under Brand Canon 2.0.0 and BrandBuilder 3.0.0, then pin each new package ID and checksum. Eight release-authorized brand archives join the skill and portable distribution in the official release; three client kits remain hosted candidates. Old 2.8.0 packages remain immutable recovery artifacts." + "summary": "Repinning BrandBuilder 3.0.1 requires its exact new package IDs and checksums. The verified site hosts twelve brands; nine owned archives join the skill and portable distribution, while three client kits remain hosted candidates. Previously published packages remain immutable recovery inputs." } } } diff --git a/skill/templates/build_specimen.py b/skill/templates/build_specimen.py index aacd081..1fdc244 100644 --- a/skill/templates/build_specimen.py +++ b/skill/templates/build_specimen.py @@ -20,6 +20,8 @@ from fontTools.ttLib import TTFont from fontTools.pens.svgPathPen import SVGPathPen from fontTools.pens.transformPen import TransformPen +from fontTools.pens.boundsPen import BoundsPen +from fontTools.svgLib.path import parse_path from brand_contract import _image_dimensions, font_face_path, specimen_mark_paths, typography_families @@ -63,21 +65,47 @@ def embedded_image_uri(kit, relative): encoded = base64.b64encode(payload).decode("ascii") return "data:%s;base64,%s" % (media_type, encoded), source_hash -def outlined_text(text, font_path, size, x, baseline, fill): +def outlined_text(text, font_path, size, x, baseline, fill, center_y=None): font = TTFont(font_path) glyphs, cmap = font.getGlyphSet(), font.getBestCmap() scale = size / font["head"].unitsPerEm cursor, paths = x, [] + bounds = BoundsPen(glyphs) if center_y is not None else None for char in text: name = cmap.get(ord(char), ".notdef") pen = SVGPathPen(glyphs) glyphs[name].draw(TransformPen(pen, (scale, 0, 0, -scale, cursor, baseline))) + if bounds is not None: + glyphs[name].draw(bounds) d = pen.getCommands() if d: paths.append(d) cursor += font["hmtx"].metrics[name][0] * scale font.close() - return '' % (fill, " ".join(paths)) + placement = "" + if bounds is not None and bounds.bounds is not None: + ink_center = baseline - (bounds.bounds[1] + bounds.bounds[3]) * scale / 2.0 + placement = ' transform="translate(0,%s)"' % placement_number(center_y - ink_center) + return '' % (fill, " ".join(paths), placement) + + +def mark_center_y(brand, kit, y, height): + """Measure the governed mark's vertical ink center without rewriting its paths.""" + boxes = [] + for item in specimen_mark_paths(brand, kit): + if item.get("element", "path") == "path": + pen = BoundsPen(None) + parse_path(item["d"], pen) + box = pen.bounds + else: + top = float(item.get("y", 0)) + box = (0, top, 0, top + float(item["height"])) + if box is not None: + stroke = float(item.get("stroke_width", 0)) / 2.0 + boxes.append((box[1] - stroke, box[3] + stroke)) + grid = float((brand.get("logo") or {}).get("grid", 1000)) + center = (min(box[0] for box in boxes) + max(box[1] for box in boxes)) / 2.0 if boxes else grid / 2.0 + return y + center * height / grid def clip(text, n): @@ -160,14 +188,15 @@ def main(): display_sample = messages.get("slogan", title) body_sample = messages.get("short_description", "Aa Bb Cc Dd Ee Ff Gg") fams = families + header_center = mark_center_y(B, kit, 66, 170) parts = [ '', '' % base, mark(B, kit, 66, 66, 170), - outlined_text("%s TYPE SYSTEM" % title.upper(), mono, 20, 260, 110, dim), - outlined_text(title, display, 154, 252, 365, "#FFFFFF"), - outlined_text(clip(body_sample, 86), body, 34, 258, 462, dim), + outlined_text("%s TYPE SYSTEM" % title.upper(), mono, 20, 82, 55, dim), + '%s' % outlined_text(title, display, 154, 252, 0, "#FFFFFF", center_y=header_center), + outlined_text(clip(body_sample, 86), body, 34, 258, 340, dim), '' % rule, outlined_text("DISPLAY / %s BOLD" % fams["display"]["name"].upper(), mono, 18, 82, 600, acc), outlined_text(display_sample, display, 60, 82, 682, "#FFFFFF"), @@ -179,7 +208,8 @@ def main(): '', ] dest = out / ("%s-type-specimen.svg" % B["slug"]) - dest.write_text("\n".join(parts) + "\n", encoding="utf-8") + with dest.open("w", encoding="utf-8", newline="\n") as handle: + handle.write("\n".join(parts) + "\n") print("wrote %s" % dest) diff --git a/skill/templates/interface_contract.py b/skill/templates/interface_contract.py index c3670fe..533a0cc 100644 --- a/skill/templates/interface_contract.py +++ b/skill/templates/interface_contract.py @@ -45,6 +45,7 @@ "eso-weave", "cueson", "local-companion", + "insonic", ) BACKWARD_BRAND_DEFAULTS = { "surfaces.base": "#000000", diff --git a/skill/templates/qc_images.py b/skill/templates/qc_images.py index d18bab3..12c5ac0 100644 --- a/skill/templates/qc_images.py +++ b/skill/templates/qc_images.py @@ -96,7 +96,7 @@ def log_message(self, format, *args): thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() try: - with sync_playwright() as pw: + with tempfile.TemporaryDirectory(prefix="brandkit-qc-") as temporary, sync_playwright() as pw: b = pw.chromium.launch() for p in pages: name = os.path.splitext(os.path.basename(p))[0] @@ -104,8 +104,8 @@ def log_message(self, format, *args): rel = os.path.relpath(p, kit).replace(os.sep, "/") u = "http://127.0.0.1:%d/%s" % (server.server_port, quote(rel)) dk = b.new_page(viewport={"width": 1280, "height": 900}); dk.goto(u); dk.wait_for_timeout(1400) - desktop_path = os.path.join(tempfile.gettempdir(), "_brandkit_qc_desktop.png") - mobile_path = os.path.join(tempfile.gettempdir(), "_brandkit_qc_mobile.png") + desktop_path = os.path.join(temporary, "desktop.png") + mobile_path = os.path.join(temporary, "mobile.png") dk.screenshot(path=desktop_path, full_page=True) mb = b.new_page(viewport={"width": 390, "height": 844}); mb.goto(u); mb.wait_for_timeout(1100) mb.screenshot(path=mobile_path, full_page=False) diff --git a/skill/templates/test_brand_essentials.py b/skill/templates/test_brand_essentials.py index fa25fee..c5429b2 100644 --- a/skill/templates/test_brand_essentials.py +++ b/skill/templates/test_brand_essentials.py @@ -56,7 +56,7 @@ def test_source_visual_fields_and_sparse_brand(self): def test_all_production_brands_have_source_bound_essentials(self): sources = sorted((ROOT / "brands").glob("*/brand.json")) - self.assertEqual(11, len(sources)) + self.assertEqual(12, len(sources)) for source in sources: with self.subTest(source=source): brand = json.loads(source.read_text(encoding="utf-8")) diff --git a/skill/templates/test_interface_contract.py b/skill/templates/test_interface_contract.py index 60b9bae..e79f0c3 100644 --- a/skill/templates/test_interface_contract.py +++ b/skill/templates/test_interface_contract.py @@ -118,7 +118,7 @@ def test_version_policy_has_independent_domains_and_fails_incompatible_combinati def test_release_impact_is_closed_and_rejects_downstream_evidence_fields(self): impact = load_release_impact() - self.assertEqual("3.0.0", impact["brandbuilder_version"]) + self.assertEqual("3.0.1", impact["brandbuilder_version"]) self.assertFalse(impact["identity_redesign"]) self.assertIn("unchanged", impact["surfaces"]["identity"]["summary"]) self.assertEqual( @@ -362,7 +362,7 @@ def test_emitted_contract_is_deterministic_verifiable_and_offline_complete(self) self.assertEqual("1.0.0", first["versions"]["wordpress_adapter_version"]) self.assertEqual("compatible", first["compatibility"]["status"]) self.assertNotIn("adoption_status", first["compatibility"]) - expected_package = "shruggietech-brand-%s-bb3.0.0" % brand["version"] + expected_package = "shruggietech-brand-%s-bb3.0.1" % brand["version"] self.assertEqual(expected_package, first["bundle"]["package"]["id"]) self.assertEqual(expected_package + ".zip", first["bundle"]["package"]["filename"]) self.assertEqual(brand["version"], first["bundle"]["package"]["brand_version"]) diff --git a/skill/templates/test_messaging.py b/skill/templates/test_messaging.py index 3b0e82a..edefac7 100644 --- a/skill/templates/test_messaging.py +++ b/skill/templates/test_messaging.py @@ -71,7 +71,7 @@ def test_approved_roles_are_independent(self): def test_current_brands_classify_core_roles(self): sources = sorted(ROOT.glob("brands/*/brand.json")) - self.assertEqual(11, len(sources)) + self.assertEqual(12, len(sources)) schema = json.loads((ROOT / "skill/references/canon.schema.json").read_text(encoding="utf-8")) for source in sources: with self.subTest(source=source.parent.name): diff --git a/skill/templates/test_pipeline.py b/skill/templates/test_pipeline.py index d565c42..f9f6fa1 100644 --- a/skill/templates/test_pipeline.py +++ b/skill/templates/test_pipeline.py @@ -7,6 +7,7 @@ import copy import hashlib from contextlib import redirect_stdout +from concurrent.futures import ThreadPoolExecutor from io import BytesIO, StringIO import os import re @@ -61,6 +62,25 @@ def require_svg_renderer(self): if not any(shutil.which(name) for name in ("rsvg-convert", "resvg", "inkscape")) and not probe.node_resvg_ok(): self.skipTest("SVG rasterizer unavailable at core tier") + def test_concurrent_page_qc_preserves_each_kit_screenshots(self): + chromium, reason = probe.chromium_ok() + if not chromium: + self.skipTest(reason) + with tempfile.TemporaryDirectory() as temporary: + kits = [] + for slug, color in (("red", "#A00000"), ("blue", "#0000A0")): + kit = Path(temporary) / slug + (kit / "qc").mkdir(parents=True) + write_utf8(kit / "index.html", '' % color) + kits.append(kit) + with ThreadPoolExecutor(max_workers=2) as executor: + sheets = list(executor.map(lambda kit: qc_images.page_shots(str(kit), str(kit / "qc")), kits)) + for files, expected in zip(sheets, ((160, 0, 0), (0, 0, 160))): + self.assertEqual(len(files), 1) + with Image.open(files[0]) as image: + self.assertEqual(image.getpixel((50, 100)), expected) + self.assertEqual(image.getpixel((780, 100)), expected) + def test_portable_override_guidance_shows_theme_defaults_and_effective_reference(self): facts = {"schema_version": 1, "documentation_contract_version": "1.1.0", "brand": {"slug": "sample"}, "versions": {}, "bindings": {}, "rules": {"inheritance": "independent", @@ -227,6 +247,39 @@ def build_image_specimen(self, brand_path): brand = json.loads(brand_path.read_text(encoding="utf-8")) return brand_path.parent / "specimens" / (brand["slug"] + "-type-specimen.svg") + def test_type_specimen_header_aligns_rendered_mark_and_name_ink(self): + self.require_svg_renderer() + with tempfile.TemporaryDirectory() as temporary: + kit = Path(temporary) / "specimen-image-fixture" + brand_path, source = self.image_specimen_fixture(kit) + brand = json.loads(brand_path.read_text(encoding="utf-8")) + brand["surfaces"]["base"] = "#000000" + brand["accent"]["bright"] = "#C5342C" + for title, paths in ( + ("insonic", brand["logo"]["paths"]["full"]), + ("gyp", [{"role": "accent", "d": "M10 20H90V90H10Z"}]), + ): + with self.subTest(title=title): + brand["title"] = title + brand["logo"]["paths"]["full"] = paths + write_utf8(brand_path, json.dumps(brand, indent=2) + "\n") + specimen = self.build_image_specimen(brand_path) + self.assertNotIn(b"\r", specimen.read_bytes()) + rendered = kit / "header-check.png" + gen_logo.raster(["-w", "1600", str(specimen), "-o", str(rendered)]) + with Image.open(rendered) as image: + pixels = image.convert("RGB") + mark_rows = [y for y in range(66, 236) for x in range(66, 236) + if pixels.getpixel((x, y))[0] > 150 + and max(pixels.getpixel((x, y))[1:]) < 80] + title_rows = [y for y in range(66, 236) for x in range(252, 1200) + if min(pixels.getpixel((x, y))) > 240] + self.assertTrue(mark_rows, "header mark must be visible") + self.assertTrue(title_rows, "header name must share the mark row") + mark_center = (min(mark_rows) + max(mark_rows)) / 2.0 + title_center = (min(title_rows) + max(title_rows)) / 2.0 + self.assertLessEqual(abs(mark_center - title_center), 1.0) + def test_image_specimen_is_self_contained_source_exact_and_fail_closed(self): with tempfile.TemporaryDirectory() as temporary: kit = Path(temporary) / "specimen-image-fixture" diff --git a/specs/068-brand-essentials-docs-navigation/disposition-inventory.md b/specs/068-brand-essentials-docs-navigation/disposition-inventory.md index ea2429a..53e4445 100644 --- a/specs/068-brand-essentials-docs-navigation/disposition-inventory.md +++ b/specs/068-brand-essentials-docs-navigation/disposition-inventory.md @@ -1,6 +1,6 @@ -# S068 eleven-brand sentence disposition +# Brand essentials sentence disposition -Every current first-page legacy field, array item, and sentence is listed below with its exact source text and a reviewed disposition. The source remains intact for product-documentation or messaging decisions; this slice changes which fields visual guides project. The current production inventory has eleven brands, superseding the eight-brand issue intake count. Approval for one surface is not approval for another. +Introduced by S068 for eleven brands and extended by S070 on 2026-10-03 with the owner's approved insonic guidance and messages. Every current first-page legacy field, array item, and sentence is listed below with its exact source text and a reviewed disposition. The source remains intact for product-documentation or messaging decisions; this slice changes which fields visual guides project. The current production inventory has 12 brands. Approval for one surface is not approval for another. | Brand | Source path | Exact sentence or item | Classification | Disposition | | --- | --- | --- | --- | --- | @@ -315,6 +315,32 @@ Every current first-page legacy field, array item, and sentence is listed below | i-heart-pr-tours | `guidance.personality[2][1]` | `"Plans well while leaving room to enjoy the place"` | voice guidance | Retain in canonical source and dedicated Voice guidance; omit from the PDF identity opening | | i-heart-pr-tours | `guidance.personality[2][2]` | `"Artificial urgency"` | voice guidance | Retain in canonical source and dedicated Voice guidance; omit from the PDF identity opening | | i-heart-pr-tours | `messaging.short_description` | `"Thoughtfully guided tours on the island we love."` | approved for another surface | Project only on the declared surfaces: site-metadata | +| insonic | `title` | `"insonic"` | approved identity name | Project as the approved name | +| insonic | `affiliation.ownership` | `"shruggietech-owned"` | relationship metadata | Derive the declared relationship without inventing an endorsement | +| insonic | `affiliation.parent` | `"ShruggieTech"` | relationship metadata | Derive the declared relationship without inventing an endorsement | +| insonic | `affiliation.endorsement` | `"shruggietech-project"` | relationship metadata | Derive the declared relationship without inventing an endorsement | +| insonic | `descriptor` | `"Voice archives, speaker models, and connected knowledge."` | legacy product or strategy summary | Retain in source for product or messaging review; omit from visual guides | +| insonic | `functional_descriptor` | `"Voice archives, speaker models, and connected knowledge."` | legacy product or strategy summary | Retain in source for product or messaging review; omit from visual guides | +| insonic | `guidance.sharp_edge` | `"A connected assertion retains its source and speaker context."` | product or operational boundary | Retain in source for product documentation review; omit from visual guides | +| insonic | `guidance.sharp_edge` | `"Brand examples distinguish supplied audio, speaker references and generated audio without asserting recognition accuracy."` | product or operational boundary | Retain in source for product documentation review; omit from visual guides | +| insonic | `guidance.written_form` | `"Write insonic in lowercase."` | approved identity guidance | Project in Brand essentials or the detailed identity pages | +| insonic | `guidance.written_form` | `"Keep the product name distinct from file, speaker and assertion identifiers."` | approved identity guidance | Project in Brand essentials or the detailed identity pages | +| insonic | `guidance.logo` | `"Three separated rounded voice forms establish a clear rhythm."` | approved identity guidance | Project in Brand essentials or the detailed identity pages | +| insonic | `guidance.logo` | `"Their staggered lengths and endpoints retain individuality; a strengthened Reduced master carries all three forms into tiny applications."` | approved identity guidance | Project in Brand essentials or the detailed identity pages | +| insonic | `guidance.palette` | `"Glacial blue carries the identity on midnight surfaces; deep glacial blue carries it on light surfaces."` | approved identity guidance | Project in Brand essentials or the detailed identity pages | +| insonic | `guidance.palette` | `"State colors remain functional, with labels and symbols accompanying them."` | approved identity guidance | Project in Brand essentials or the detailed identity pages | +| insonic | `guidance.personality[0][0]` | `"Calm"` | voice guidance | Retain in canonical source and dedicated Voice guidance; omit from the PDF identity opening | +| insonic | `guidance.personality[0][1]` | `"Uses quiet surfaces and concise explanations"` | voice guidance | Retain in canonical source and dedicated Voice guidance; omit from the PDF identity opening | +| insonic | `guidance.personality[0][2]` | `"Manufactured urgency"` | voice guidance | Retain in canonical source and dedicated Voice guidance; omit from the PDF identity opening | +| insonic | `guidance.personality[1][0]` | `"Precise"` | voice guidance | Retain in canonical source and dedicated Voice guidance; omit from the PDF identity opening | +| insonic | `guidance.personality[1][1]` | `"Names sources, speakers and processing states clearly"` | voice guidance | Retain in canonical source and dedicated Voice guidance; omit from the PDF identity opening | +| insonic | `guidance.personality[1][2]` | `"Unsupported accuracy claims"` | voice guidance | Retain in canonical source and dedicated Voice guidance; omit from the PDF identity opening | +| insonic | `guidance.personality[2][0]` | `"Connected"` | voice guidance | Retain in canonical source and dedicated Voice guidance; omit from the PDF identity opening | +| insonic | `guidance.personality[2][1]` | `"Shows relationships between voices and their source material"` | voice guidance | Retain in canonical source and dedicated Voice guidance; omit from the PDF identity opening | +| insonic | `guidance.personality[2][2]` | `"Anonymous decorative graph imagery"` | voice guidance | Retain in canonical source and dedicated Voice guidance; omit from the PDF identity opening | +| insonic | `messaging.slogan` | `"Voice, with memory."` | approved identity words | Project only on the declared surfaces: visual-guide, strategy-reference, social-image | +| insonic | `messaging.short_description` | `"Voice archives, speaker models, and connected knowledge."` | approved identity words | Project only on the declared surfaces: site-metadata, visual-guide, consumer-data | +| insonic | `messaging.long_description` | `"insonic brings speaker-tagged audio into a growing archive of recordings, transcripts, and connected assertions. Its intended scope includes ingestion, storage, normalization, transcription, knowledge graph construction, speaker recognition, and voice replication, with speaker models improving as the collection grows."` | approved for another surface | Project only on the declared surfaces: site-metadata, strategy-reference, consumer-data | | local-companion | `title` | `"Local Companion"` | approved identity name | Project as the approved name | | local-companion | `affiliation.ownership` | `"shruggietech-owned"` | relationship metadata | Derive the declared relationship without inventing an endorsement | | local-companion | `affiliation.parent` | `"ShruggieTech"` | relationship metadata | Derive the declared relationship without inventing an endorsement | @@ -439,4 +465,4 @@ Every current first-page legacy field, array item, and sentence is listed below | shruggietech | `guidance.personality[3][2]` | `"Platform lock-in"` | voice guidance | Retain in canonical source and dedicated Voice guidance; omit from the PDF identity opening | | shruggietech | `messaging.slogan` | `"We’ll figure it out."` | approved identity words | Project only on the declared surfaces: visual-guide, social-image, site-metadata, consumer-data | -Total classified items: 434 across 11 production brands. +Total classified items: 460 across 12 production brands. diff --git a/specs/068-brand-essentials-docs-navigation/inventory.py b/specs/068-brand-essentials-docs-navigation/inventory.py index 7d4f668..9ed0ec2 100644 --- a/specs/068-brand-essentials-docs-navigation/inventory.py +++ b/specs/068-brand-essentials-docs-navigation/inventory.py @@ -1,4 +1,4 @@ -"""Rebuild or check the reviewed eleven-brand S068 sentence disposition.""" +"""Check the source-bound sentence disposition introduced by S068 and extended by S070.""" import argparse import json import re @@ -83,10 +83,11 @@ def render(): literal = json.dumps(text, ensure_ascii=False).replace("|", "|").replace("`", "`") rows.append(f"| {slug} | `messaging.{role}` | `{literal}` | {category} | {destination} |") lead = ( - "# S068 eleven-brand sentence disposition\n\n" + "# Brand essentials sentence disposition\n\n" + "Introduced by S068 for eleven brands and extended by S070 on 2026-10-03 with the owner's approved insonic guidance and messages. " "Every current first-page legacy field, array item, and sentence is listed below with its exact source text and a reviewed disposition. " "The source remains intact for product-documentation or messaging decisions; this slice changes which fields visual guides project. " - "The current production inventory has eleven brands, superseding the eight-brand issue intake count. " + f"The current production inventory has {len(sources)} brands. " "Approval for one surface is not approval for another.\n\n" "| Brand | Source path | Exact sentence or item | Classification | Disposition |\n" "| --- | --- | --- | --- | --- |\n" diff --git a/specs/070-insonic-brand/checklists/requirements.md b/specs/070-insonic-brand/checklists/requirements.md new file mode 100644 index 0000000..56943ba --- /dev/null +++ b/specs/070-insonic-brand/checklists/requirements.md @@ -0,0 +1,31 @@ +# Specification Quality Checklist: insonic Brand and Official Showcase + +**Purpose**: Validate completeness before planning. + +**Created**: 2026-10-03 + +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] Requirements describe owner and visitor outcomes without implementation code. +- [x] All mandatory specification sections are complete. +- [x] Full intended voice and knowledge scope is retained. + +## Requirement Completeness + +- [x] No unresolved scope clarification markers remain. +- [x] Requirements, scenarios, and measurable outcomes are testable. +- [x] Edge cases include drift, messaging inference, tiny marks, accessibility, and private data. +- [x] Boundaries distinguish brand work from engine implementation. +- [x] Creative choices remain decisions rather than falsely approved requirements. + +## Feature Readiness + +- [x] Three stories cover identity, fundamentals, and showcase delivery. +- [x] Both creative gates and publication sequencing are explicit. +- [x] Existing identity preservation and aggregate validation are required. + +## Notes + +Audience and mark association will update the working brief before production construction. They do not prevent specifying the agreed workflow. diff --git a/specs/070-insonic-brand/contracts/delivery.md b/specs/070-insonic-brand/contracts/delivery.md new file mode 100644 index 0000000..80ce4c8 --- /dev/null +++ b/specs/070-insonic-brand/contracts/delivery.md @@ -0,0 +1,11 @@ +# insonic Delivery Contract + +The final source declares lowercase `insonic`, `insonic.io`, ShruggieTech ownership/parentage, independent palette inheritance, house typography, public showcase permission, and the reviewed owned-project endorsement. Exact colors, geometry, copy and compositions require the recorded creative decisions. + +Production output uses the existing kit anatomy and supported platform matrices. Full/Reduced masters, wordmark, wide/stacked lockups, single-ink variants, native/web icon suites, local fonts, tokens/bindings, guides, manifest and verification reports derive from canonical approved source. Social artwork is a separate composition with independently decided exact copy and image digests. + +Official generated routes include the catalog, `/insonic/guidelines/brand-essentials/` and the other generated guideline sections, `/insonic/brand` registry resources, and `/conformance/insonic/`. Asset/download paths come from the generated inventory. No separate insonic.io product deployment is required in this slice. + +Public data contains approved messages and illustrative examples. Private briefs, review packets, personal media/transcripts, credentials, and biometric vectors remain outside public projection. The site does not independently restate canonical brand values. + +Publication succeeds only after both approvals, zero mandatory kit/glyph problems, aggregate validation, reviewed release workflow, and matching live routes/download checksums. Report each stage truthfully while it remains pending. diff --git a/specs/070-insonic-brand/data-model.md b/specs/070-insonic-brand/data-model.md new file mode 100644 index 0000000..e639e8f --- /dev/null +++ b/specs/070-insonic-brand/data-model.md @@ -0,0 +1,15 @@ +# S070 Data Model + +Use existing versioned schemas and runtime validators. No new schema is introduced. + +| Entity | Authority and fields | Validation | +| --- | --- | --- | +| Private brief | Thirteen topics, each with facts/constraints/proposals/unresolved arrays; independent messaging statuses; exact social-copy record. | authoring_brief validator; no candidate text in approved fields. | +| Brand source | Name/domain, owned affiliation, independent inheritance, house typography, explicit colors and messaging, logo source mode and approval ledger. | brand_contract and canon schema. | +| Geometry helper | Named grid/construction parameters and Full/Reduced paths produced through glyphkit. | helper AST/provenance validation and validate_glyph. | +| Canonical identity | Source inventory/hash, source class, framing, topology, palette qualification, renderer, 32 proofs and comparison artifacts, exact owner approval. | identity_continuity; record and normalized source digests. | +| Fundamentals packet | Current Gate 1 binding, pending Gate 2, private projection flag, exact messages, checksummed distinct required assets. | authoring_brief Gate 2 validator with approved brand root. | +| Final kit | Generated asset families, local fonts, tokens, platform contracts, guides, manifest and measured evidence. | verify and render/host checks. | +| Public projection | Generated catalog/routes/metadata/registry/assets/downloads from the verified kit. | publication/site/registry checks and live release checksum. | + +State order: exploratory, direction-selected, canonical-candidate, canonical-approved, promoted, derivative-approved, publication-eligible. Missing decisions retain pending state. Governed source drift invalidates Gate 1; reviewed derivative drift invalidates Gate 2. Every messaging role independently transitions from unresolved to approved or absent through owner wording and intended-use evidence. diff --git a/specs/070-insonic-brand/plan.md b/specs/070-insonic-brand/plan.md new file mode 100644 index 0000000..3fb8ba5 --- /dev/null +++ b/specs/070-insonic-brand/plan.md @@ -0,0 +1,110 @@ +# Implementation Plan: insonic Brand and Official Showcase + +**Branch**: `codex/070-insonic-brand` | **Date**: 2026-10-03 | **Spec**: [spec.md](spec.md) + +## Summary + +Start from the verified latest BrandBuilder 3.0.0 and ship the approved insonic identity through compiler/site 3.0.1. The adaptive brief, exploratory directions, source-bound Gate 1 and assembled Gate 2 are approved. Complete final verification and official-site release with the independent Glacial blue palette and shared local typography/layout. + +## Technical Context + +**Language/Version**: Python 3.8 minimum, local Python 3.12.9; Node.js 20 minimum. Current exploratory host has Node 26.5.0. Select and record the canonical renderer before Gate 1, preferably the existing Node 24.11.0 Windows proof group. + +**Primary Dependencies**: Existing glyphkit, identity_continuity, gen_logo, authoring_brief, promote_identity, build_all, prepare_site, Coloraide, fontTools, Pillow, pikepdf, Playwright, Node resvg, Next.js. + +**Storage**: Source-only approved brand under `brands/insonic/`; specs under this directory; exploratory output and review packets under ignored `dist/private/insonic/`; final generated kit under `dist/insonic/`. + +**Testing**: Private brief validation, glyph and palette qualification, exact source/proof continuity, Gate 2 packet validation, full kit validation, documented aggregate contract/site/publication checks, and live route/checksum verification. + +**Target Platform**: Portable brand kit and official static brand site. Host applications consume the supported bindings independently. + +**Project Type**: Brand source and catalog addition using an existing compiler and site. + +**Performance Goals**: Reproducible identity and verified delivery; no invented product latency or recognition-accuracy targets. + +**Constraints**: Two exact creative approvals, WCAG AA, no inferred messaging, source-only Git, unchanged existing geometry, hidden non-interactive Windows tooling, UTF-8 without BOM and LF. + +**Scale/Scope**: One new brand, twelve hosted identities and nine release-authorized owned archives after integration; preserve three existing client hosted-only identities. + +## Constitution Check + +| Principle | Response | Status | +| --- | --- | --- | +| P1 Source/artifact boundary | Commit specs and approved source; keep previews, proofs, binaries, kits, and exports in ignored output. | Design passes | +| P2 Geometry preservation | Construct with glyphkit, bind exact source at Gate 1, promote approved bytes and preserve siblings. | Approved source and proofs pass | +| P3 Accessibility | Measure dark/light text, controls, state and palette roles; correct failures. | insonic palette and kit pass; aggregate site checks in progress | +| P4 Verification | Final compilation follows approvals and requires zero kit/glyph problems plus aggregate validation. | insonic passes; aggregate verification in progress | +| P5 Site projection | Reuse automatic brand discovery and verified generated projection. | Design passes | +| P6 Spec/release | Maintain this slice through reviewed change, tagged CI release, and live checks. | In progress | + +No exception or parallel brand compiler is proposed. Post-design review finds no conflict; pending entries are execution gates rather than waived requirements. + +## Phase 0: Research + +Research uses the product docs and current generator/site source. The plan research subagent was explicitly requested by the installed speckit-plan workflow. Findings identify S064 Local Companion as the nearest new constructed identity workflow, the required release/site inventories, and the canonical Windows renderer concern. See [research.md](research.md). + +## Phase 1: Design and Contracts + +Use existing brand, identity continuity, messaging, and publication schemas without adding a new schema. [data-model.md](data-model.md) names authority and state transitions; [delivery.md](contracts/delivery.md) binds the public contract; [quickstart.md](quickstart.md) gives validation entry points. Creative discovery stays interactive, while the workflow and integration path are fully specified. + +## Project Structure + +```text +specs/070-insonic-brand/ + spec.md + plan.md + research.md + data-model.md + contracts/delivery.md + quickstart.md + tasks.md + verification.md + checklists/requirements.md +brands/insonic/ + brand.json + build/mk_paths.py + identity-continuity.json +dist/private/insonic/ + brief.json + directions/ + gate-1/ + gate-2/ +dist/insonic/ +``` + +**Structure Decision**: Reuse canonical sources and generated delivery conventions. No application repo, domain deployment, or processing runtime is created by this slice. + +## Integration and Verification + +After both approvals, register insonic in release authorization, publication and identity audits, site expectations, and Windows proof/kit artifact inventories. Preserve hosted-only client release policy. Site discovery and pages are generated automatically, so do not author a duplicate brand page. + +Use the current CI workflow and contributor guide as the exact aggregate-check authority. The new kit must pass current-source validation, all production proofs, glyph checks, source-bound Gate 2 validation, final build, render/pagination checks, encoding scan, site/registry/publication audits, and release checksum inspection. Existing kits require their current renderer groupings; do not rebuild siblings with a substituted approved renderer. + +Release metadata changes occur after creative work is complete and must synchronize compiler, site, changelogs, release impact and tests without altering canon/adapter versions unless their contracts change. Live deployment follows successful tagged release publication. A merged main commit alone does not satisfy the official-site outcome. + +## Decisions, Chronological + +- 2026-10-03: Use repository BrandBuilder 3.0.0, verified against the published latest release, instead of cached plugin 1.0.0. +- 2026-10-03: Adopt the accepted independent-palette/shared-typography direction and public owned-project relationship; keep exact creative decisions pending. +- 2026-10-03: Use S064's prospective identity continuity workflow and existing generated site discovery. Do not copy historical custom concept-rendering assumptions into production. +- 2026-10-03: Select canonical renderer before Gate 1 and bind its version and settings; exploratory previews are nonbinding. +- 2026-10-03: Owner selects direction A and rejects the messy crossing bar. Compare removing the bar against moving the connection to a common terminal baseline, retaining the provisional palette to isolate the geometry decision. +- 2026-10-03: Owner selects A1 Clean rhythm and requests color comparisons on that geometry before choosing production colors. Preserve the selected three forms across comparison panels. +- 2026-10-03: Owner selects Glacial blue and retains all three forms in Reduced with stronger tiny-size stems. Construct Full with the exact selected path array and Reduced with 160-unit stems on the same centers and heights. Bind the canonical Windows Node 24.11.0 renderer group before source approval. +- 2026-10-03: Owner approves the exact Gate 1 packet. Record exact wording and source-bound evidence, promote canonical source atomically, and verify all current production proof and comparison hashes before provisional derivative generation. +- 2026-10-03: Owner selects developers and the slogan direction `Voice, with memory.`. Prepare source-derived lockups and type, distinct functional cues, a synthetic developer-workspace example and an exact social-copy proposal. Use the existing classic social layout because the centered-single-line preset crowds this lockup. Confirm independent message roles/uses and social line breaks before validating the formal Gate 2 packet. +- 2026-10-03: Owner accepts the shown fundamentals/copy and instructs continuation. Record exact message-role and social-copy decisions in the source and private brief, preserving the governed Gate 1 snapshot. Generate the canonical social SVG/PNG and outlined specimen, validate the formal source-bound Gate 2 packet, then request the final assembled creative approval. +- 2026-10-03: Owner identifies a confusing mark/name misalignment in the generated typography specimen. Correct the upstream specimen header using measured mark and font ink centers, regenerate its source/raster, and refresh the pending Gate 2 packet. Preserve approved logo geometry, existing logo derivatives and social composition. +- 2026-10-03: Owner approves the revised complete packet with "approved. CONTINUE". Record Gate 2 and exact social-image digests without changing the canonical Gate 1 binding. Integrate insonic 1.0.0 in BrandBuilder/site 3.0.1, a compiler patch for the specimen corrections; canon and adapter versions remain unchanged. Track publication through issue #319 and the reviewed tagged release. +- 2026-10-03: Connect the exact approved naming, personality, logo and palette narrative to the existing `guidance` fields consumed by the official guide. This corrects an omitted projection without introducing new copy or changing the approved identity snapshot. Extend the source-bound S068 disposition inventory and current production-count assertions to twelve brands. +- 2026-10-03: Concurrent aggregate builds expose shared global temporary screenshot filenames in `qc_images.py`. Give each QC run its own temporary directory and cover actual simultaneous red/blue kit captures in the pipeline regression suite. Rebuild affected distributions with the synchronized release-impact record. +- 2026-10-03: The required WordPress fixture dependency audit fails on newly reviewed GHSA-ch52-4w7c-c8xp through `@wordpress/env` 11.16.0, `got`, `cacheable-request` and `http-cache-semantics` 4.2.0. Both installed packages match the latest published versions and the advisory lists no patched release. Preserve the required audit gate and seek owner direction before expanding this brand slice into dependency remediation. Official publication remains pending. +- 2026-10-03: Owner states "Yes, remediate the test dependency". The pinned runner uses only JSON retrieval and streaming ZIP downloads from got, with no cache configuration. Replace that fixture dependency with a local built-in-fetch compatibility transport, eliminating the affected cache chain rather than suppressing the advisory. Cover the two consumed APIs, cookie/cache isolation, redirects, exact download bytes, HTTP/truncation errors, cancellation and actual installed runner resolution. Enforce these tests before the unchanged npm audit and pinned runtime checks in CI. The transport remains outside generated consumer kits. + +## Complexity Tracking + +Automated PR review identifies an unresolved library import when the canonical construction helper is executed directly. The canonical source binds that helper's exact bytes; the logo protocol requires a fresh approval if those bytes change. Supply the dependency through a separate `build/run_paths.py` launcher instead, with automatic repository discovery or explicit installed templates for standalone kits. Verify direct execution from an unrelated directory, a staged standalone kit, actionable missing-library errors, unchanged helper hash and exact path reproduction. This operational fix preserves both creative approvals and all canonical geometry. + +Artifact checks identified platform-default CRLF serialization in the outlined typography generator. Correct that upstream in `skill/templates/build_specimen.py`, document it in the kit build notes, and verify LF output on Windows without changing rendered geometry. + +No constitution violations or architectural expansion identified. diff --git a/specs/070-insonic-brand/quickstart.md b/specs/070-insonic-brand/quickstart.md new file mode 100644 index 0000000..9ce2551 --- /dev/null +++ b/specs/070-insonic-brand/quickstart.md @@ -0,0 +1,12 @@ +# S070 Validation Guide + +Prerequisites: repository Python environment and bundled fonts; measured renderer and full-tier probe; hidden non-interactive launch for non-Git console tools on Windows. Follow current CONTRIBUTING.md and .github/workflows/build.yml for the complete release gate. + +1. Validate the private brief with `skill/templates/authoring_brief.py dist/private/insonic/brief.json`. Current records carry explicit owner decisions for both creative gates. Historical review-stage assets remain immutable. +2. Run `skill/templates/validate_glyph.py` on the candidate construction helper. Expect zero failures; explain any permitted warnings and inspect the render. +3. Generate the production 32-proof matrix and full Gate 1 source/palette/renderer evidence, inspect it, and record exact owner approval before promotion. +4. Validate the private Gate 2 packet using `authoring_brief.py BRIEF --gate-2 PACKET --brand-root brands/insonic`. Expect current Gate 1 binding and distinct complete checksummed derivatives. Record owner Gate 2 and social image approval before final build. +5. Build the final kit with `scripts/build_all.py insonic`; expect zero verify problems and zero glyph failures. Inspect logo/contact sheets, guide, application and social outputs; run applicable render/pagination checks. +6. Run the documented aggregate contract tests and renderer-grouped production builds, source audits, registry delivery checks, site lint/build/test, publication audits, encoding/mojibake scan, and `git diff --check`. + Run the WordPress fixture's frozen install, `npm test --prefix scripts/wordpress-runtime`, unchanged moderate-threshold npm audit and both pinned runtime pairs. The owner-authorized uncached transport replaces the affected test-only HTTP-cache chain. +7. After the reviewed tagged release and site deployment, check official routes, archive inventory, and live SHA-256 agreement. Pending CI or routes do not satisfy completion. diff --git a/specs/070-insonic-brand/research.md b/specs/070-insonic-brand/research.md new file mode 100644 index 0000000..6116315 --- /dev/null +++ b/specs/070-insonic-brand/research.md @@ -0,0 +1,49 @@ +# S070 Research + +Date: 2026-10-03. Product evidence is read-only documentation, not a runtime audit or a public accuracy claim. + +## Product and Edition + +**Decision**: Use repository BrandBuilder 3.0.0 and Brand Canon 2.0.0. Local HEAD and fetched main are `99245f87b4a1ceca8ecf35b0245dd0eb6916f4b4`; latest published release is v3.0.0, dated 2026-09-30. The cached plugin skill is 1.0.0. + +**Rationale**: The owner requests the latest edition and current source supports prospective continuity, independent palettes, and separately approved messaging. + +**Alternatives considered**: Cached skill would lose required current contracts and is unsuitable. + +Recordings README, ROADMAP, and S003 research roadmap describe preservation, normalization, transcription, identities, graph planning, and a distinct voice-production branch. Facet README, ARCHITECTURE, and VOICE-PROFILES describe source-grounded assertions, voice attribution, reusable references, resumable ingest, and contextual reasoning. No personal media, transcript contents, credentials, or embeddings are needed. + +## Construction and Approvals + +**Decision**: Follow S064 Local Companion's new glyphkit-constructed workflow; retain private exploratory directions, construct production masters, qualify the palette and renderer, and request exact Gate 1 before promotion. + +**Rationale**: It matches a new owned independently colored identity. The production helper uses allowed primitives, never literal path strings or a custom serializer. + +**Alternatives considered**: S026's older custom concept renderer provides useful presentation ideas but does not supply the current production-source contract. Authoritative supplied mode is inappropriate because the owner has no existing marks. + +Gate 2 reviews exact derivatives, independent messaging uses, and a distinct social image. Preserve unresolved states and explicit absence. General construction authorization does not approve either packet. + +## Tooling and Renderer + +**Decision**: Use hidden Python process launch with redirected output and existing project subprocess guarantees. The probe reports full capability tier. Bind the canonical Windows renderer before production proof approval, preferably Node 24.11.0 and resvg 2.6.2 with the existing pinned Python dependencies. + +**Rationale**: Current exploratory PATH exposes Node 26.5.0, while current CI maintains two approved-renderer groups. Continuity binds renderer and settings, so an unrecorded change would invalidate proofs. + +**Alternatives considered**: Treating an exploratory screenshot as a production proof or silently substituting a renderer is invalid. The new brand could deliberately bind Node 26.5.0 if chosen and reflected consistently in CI. + +## Public Delivery + +**Decision**: Reuse automatic discovery in build_all and prepare_site. Add insonic explicitly to `interface_contract.py` release authorization, publication audits/tests, identity-class audit (excluding it from historical migration baselines), site expected slugs, and Windows proof export/upload inventories. + +**Rationale**: This adds one owned identity while preserving existing hosted-only client boundaries. Current eleven hosted brands/eight owned archives become twelve/nine. + +**Alternatives considered**: A handwritten product page duplicates generated brand authority. Expanding client archive eligibility is unrelated and unapproved. + +The registry base is `https://brand.shruggie.tech/insonic/brand`. The generated guideline destinations are `/insonic/guidelines/brand-essentials/`, `/logo/`, `/color/`, `/typography/`, `/voice/`, and `/assets/` under the same guidelines prefix. Conformance lives at `/conformance/insonic/`. Do not assume `/insonic/` exists. Formal release publication triggers site deployment; merging main alone does not. + +## Verification + +**Decision**: Use existing checks rather than new implementation-mirroring tests. Qualify the brief, geometry, palette, and approval packets before final compilation; afterward run new-kit and aggregate production/site/publication validation from CONTRIBUTING and current CI. + +**Rationale**: The task adds an identity through existing contracts. New regression tests are warranted only if a generator defect requires an upstream fix. + +**Alternatives considered**: Passing only the new logo gate does not establish a usable kit or public-site completion. diff --git a/specs/070-insonic-brand/spec.md b/specs/070-insonic-brand/spec.md new file mode 100644 index 0000000..4b6703f --- /dev/null +++ b/specs/070-insonic-brand/spec.md @@ -0,0 +1,122 @@ +# Feature Specification: insonic Brand and Official Showcase + +**Feature Branch**: `codex/070-insonic-brand` + +**Created**: 2026-10-03 + +**Status**: Both creative gates approved, final verification and official publication in progress + +**Input**: The owner requests a new ShruggieTech brand for `insonic`, with domain `insonic.io`, constructed from the ground up using the latest BrandBuilder. Intended scope: ingest, store, normalize, transcribe, and organize speaker-tagged voice assertions into a knowledge graph while improving speaker recognition and replication with the collection. The owner authorizes Spec Kit construction, interactive questions, and official company brand-subdomain inclusion upon completion. + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Choose and approve the identity (Priority: P1) + +The owner reviews a product-grounded brief and visual directions, selects a direction, and approves its actual production Full and Reduced masters with exact source and measured proofs. + +**Why this priority**: Every later artifact needs an explicitly approved identity. + +**Independent Test**: Inspect the selected direction, source inventory, four sizes on four surfaces for both masters, and exact owner decision. + +**Acceptance Scenarios**: + +1. **Given** the product description and source documentation, **When** the brief is prepared, **Then** it covers preservation, transcription, speaker identity, assertions, graphs, recognition, and replication while distinguishing current and planned capabilities. +2. **Given** exploratory directions, **When** the owner selects one, **Then** selection authorizes candidate construction without constituting production-source approval. +3. **Given** a qualified candidate, **When** Gate 1 is approved, **Then** the decision binds exact masters, construction method, palette, framing, renderer, and 32 proofs. + +### User Story 2 - Approve and receive brand fundamentals (Priority: P2) + +The owner reviews lockups, typography, formal colors, interface cues, a representative application, and a distinct social image, then receives the verified kit. + +**Why this priority**: Consistent, readable applications and independently approved copy make the identity usable. + +**Independent Test**: Review the Gate 2 packet and confirm the final kit has zero mandatory verification problems, zero glyph failures, and inspected visual evidence. + +**Acceptance Scenarios**: + +1. **Given** Gate 1 approval, **When** fundamentals are assembled, **Then** derivatives preserve approved geometry and formal colors are distinct from functional cues. +2. **Given** candidate messaging, **When** wording and uses are approved, **Then** only approved roles publish that text; unresolved candidates remain private. +3. **Given** Gate 2 approval, **When** the kit is compiled, **Then** mandatory checks pass and optional capability skips name their missing tools. + +### User Story 3 - Find the brand on the official site (Priority: P3) + +A visitor finds insonic in the ShruggieTech brand catalog, inspects approved guidelines, and downloads the matching kit and assets. + +**Why this priority**: The owner explicitly requests official publication upon completion. + +**Independent Test**: Check catalog, guidelines, registry, social metadata, downloads, and release checksums after validation and publication. + +**Acceptance Scenarios**: + +1. **Given** an approved verified kit, **When** it enters the official catalog, **Then** the site projects the same identity, message roles, assets, and version. +2. **Given** the public entry, **When** visitors follow downloads, **Then** links resolve and kit checksums match the release inventory. +3. **Given** private research and review files, **When** the site builds, **Then** those files and personal audio-project data stay outside the public projection. + +### Edge Cases + +- General green lights and direction selection do not substitute for exact creative approvals. +- Production-source, palette, framing, renderer, or proof drift returns to Gate 1; reviewed derivative or social-copy drift returns to Gate 2. +- Tiny marks retain recognizable silhouettes and intentional separation at 16 and 32 pixels. +- Unknown speakers, quoted positions, and similarity scores do not become unsupported identity, truth, or accuracy claims. +- Public specimens use illustrative data rather than personal media, transcript contents, credentials, or voice vectors. +- Failing accessible values change rather than receiving waivers. +- Owning insonic.io does not require product-site deployment in this brand slice. + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-001**: The brief MUST separate facts, constraints, proposals, and unresolved choices and cover the complete intended product scope. +- **FR-002**: The identity MUST use `insonic`, `insonic.io`, ShruggieTech ownership and parentage, and the owner-authorized official showcase relationship. +- **FR-003**: The owner MUST receive concrete directions and actual Full/Reduced masters with all 32 proofs before Gate 1 approval. +- **FR-004**: Approved geometry and provenance MUST survive promotion and generation unchanged; governed drift MUST invalidate approval. +- **FR-005**: Gate 2 MUST present wide/stacked lockups, applicable wordmark, formal palette, separate interface cues, typography, representative application, distinct social image, and derivative evidence. +- **FR-006**: Slogan, descriptions, optional strategy messages, social copy, and uses MUST retain independent approved, absent, or unresolved states; no role may silently supply another. +- **FR-007**: Text and meaningful cues MUST meet WCAG 2.1 AA on declared dark/light surfaces and communicate state beyond color alone. +- **FR-008**: Final compilation MUST follow both approvals and deliver identity assets, local fonts, tokens, supported bindings, guides, and zero mandatory verification problems. +- **FR-009**: Official catalog, guidelines, registry, metadata, and downloads MUST consume the verified kit and exclude unapproved candidates. +- **FR-010**: Public material MUST exclude private review and personal project data; source and downloadable text MUST be valid UTF-8 without BOM, LF, and free of mojibake. +- **FR-011**: Existing identities MUST preserve geometry and pass aggregate validation; generated output MUST stay outside version control. +- **FR-012**: Publication MUST follow reviewed change and release workflows, with definitive CI and live route/checksum evidence before official-site completion is claimed. + +### Key Entities + +- **Working brief**: Discovery topics, independently decided message roles, evidence, and open choices. +- **Identity candidate**: Full/Reduced masters, helper, framing, palette qualification, renderer, and proofs. +- **Approval record**: Owner wording, source, date, scope, candidate identity, and bound digests. +- **Fundamentals packet**: Derivatives, palettes, typography, application, social composition, and manifest. +- **Verified kit**: Versioned generated assets/bindings with measured results and exact inventory. +- **Public projection**: Official catalog, brand routes, approved copy/assets, and matching downloads. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: Both approvals bind actual packets, with all 32 logo proofs and no inferred approval. +- **SC-002**: The kit reports zero mandatory verification problems and zero glyph failures; produced visual evidence is inspected. +- **SC-003**: Every public message has an independently approved role/use and no specimen includes personal source-project data. +- **SC-004**: Declared official brand routes/downloads resolve to the approved version and downloadable checksums match the release inventory. +- **SC-005**: Existing kits pass aggregate validation and no generated kit, PDF, raster export, registry, or archive is committed. + +## Assumptions + +- The owner has approved lowercase identity, independent Glacial blue, shared house typography/layout, calm precise character, exact production geometry, message roles/uses, applications and both complete creative packets. +- Official company brand-subdomain publication is authorized upon completion, with the approved `A ShruggieTech project` endorsement and no separate service credit. +- Developers building voice and knowledge systems are the approved primary audience. Creative approvals bind the actual packets and source digests recorded below. +- Recordings and facet are read-only product evidence. Processing-engine implementation, audio migration, source-project changes, and actual app integration are outside this brand slice. +- Initial specimens cover brand web use and an audio-library application. Supported platform assets follow the existing kit contract. + +## Clarifications + +- 2026-10-03: The owner states, "This sub-brand will be added to our official company brand subdomain upon completion" and authorizes construction with interactive questions as needed. This establishes publication intent and construction authority while both exact creative approvals remain pending. +- 2026-10-03: The owner states, "A is best but needs to be adjusted. The cross bar that bisects the wave forms looks overly messy". Select direction A for refinement and resolve the crossing element before production construction. This is direction feedback, not Gate 1 approval. Palette and audience choices remain open. +- 2026-10-03: The owner selects "A1", the three separated voice forms with the crossbar removed, then requests palette comparisons using that mark. A1 is the selected construction direction. Palette and exact production-source approval remain pending. +- 2026-10-03: The owner selects "Glacial blue" after viewing the A1 palette comparison. Bind pale blue `#86BDF2` on dark and deep blue `#256DA9` on light to the production candidate. Exact source approval remains pending. +- 2026-10-03: The owner chooses "Keep all three; strengthen them for tiny sizes". Retain the three forms in Reduced and widen its stems from 125 to 160 units. This explicit choice supersedes the construction manual's default element-deletion recommendation for this minimal mark. Gate 1 will review the resulting exact masters and proofs. +- 2026-10-03: The owner states "Approved as provided" in response to the exact Gate 1 packet with SHA-256 `a0a00d7abac8f5b5bc31fe7183bbdd6b48d4ec37e744271581a0771e963a6da5`. Record canonical approval and promote the source without reconstructing geometry. Gate 2 remains pending. +- 2026-10-03: The owner selects "Developers building voice and knowledge systems" as the primary audience and "Voice, with memory." as the slogan direction for the fundamentals preview. Develop exact message-role and social-layout proposals without treating direction selection as approval of their public uses. +- 2026-10-03: In response to the shown private fundamentals, exact social layout and three message-role questions, the owner states "Everything is looking perfect. CONTINUE". Adopt the proposed exact slogan, short description and long description with their named uses, the single-line slogan-only classic social composition, and shown functional cues. Build and validate the formal production-bound Gate 2 packet before final compilation. Its assembled approval remains pending. +- 2026-10-03: The owner identifies misalignment between the mark and name in the generated typography specimen. Correct the specimen layout in the upstream generator, then regenerate and validate the pending assembled packet. This feedback does not change approved identity source or approve Gate 2. +- 2026-10-03: After inspecting the corrected specimen, the owner states "That looks much better". Retain the measured header alignment and present the revised complete packet for final assembled approval. +- 2026-10-03: The owner states "approved. CONTINUE" for the revised complete Gate 2 packet SHA-256 `fc80f4ef0c21c88a9166a6044838b081b005d3aa8993402e6f32084c209d9acf`. Record all eight publication surfaces, the exact derivative manifest and canonical social SVG/PNG approval, then compile, verify and publish through the official release workflow. +- 2026-10-03: After the required WordPress fixture audit fails on an unpatched transitive HTTP-cache advisory, the owner states "Yes, remediate the test dependency". Expand the test-infrastructure scope to remove that dependency chain while retaining the full npm audit and both pinned WordPress/PHP runtime gates. This does not change brand behavior or consumer bindings. diff --git a/specs/070-insonic-brand/tasks.md b/specs/070-insonic-brand/tasks.md new file mode 100644 index 0000000..5ef55d2 --- /dev/null +++ b/specs/070-insonic-brand/tasks.md @@ -0,0 +1,73 @@ +# Tasks: insonic Brand and Official Showcase + +**Input**: [spec.md](spec.md), [plan.md](plan.md), [research.md](research.md), [data-model.md](data-model.md), and [delivery contract](contracts/delivery.md). + +**Organization**: Execute in chronological phases. Checks use existing validators and meaningful delivery tests. The bounded upstream specimen correction receives rendered alignment and source-preservation regression coverage. + +## Phase 1: Setup + +- [x] T001 Initialize `specs/070-insonic-brand/spec.md`, feature state, and `codex/070-insonic-brand` through installed Spec Kit conventions. +- [x] T002 Resolve scope clarifications and validate `specs/070-insonic-brand/checklists/requirements.md`. +- [x] T003 Prepare `specs/070-insonic-brand/plan.md`, `research.md`, `data-model.md`, `contracts/delivery.md`, and `quickstart.md`. + +## Phase 2: Foundation + +- [x] T004 Probe hidden toolchain and preserve `dist/private/insonic/qc/probe.json` with capability and renderer findings. +- [x] T005 Run cross-artifact analysis on `specs/070-insonic-brand/spec.md`, `plan.md`, and `tasks.md` before construction. All twelve requirements map to execution tasks; no constitution conflict or unmapped task was found. + +## Phase 3: User Story 1, Identity Direction and Canonical Source + +**Goal**: Select a concrete direction and approve the exact production identity. + +**Independent Test**: Owner decision binds qualified production masters and the complete 32-proof matrix, without inferred approval. + +- [x] T006 [US1] Update and validate `dist/private/insonic/brief.json` with accepted affiliation, inheritance, typography, audience, and direction decisions (FR-001, FR-002). Developer audience is explicit; unresolved copy/composition choices retain pending status. +- [x] T007 [US1] Create and inspect exploratory direction/palette previews in `dist/private/insonic/directions/`, retaining nonbinding status (FR-003). Three glyphkit concepts and measured palette pairs are rendered in `direction-sheet.png`; production Full/Reduced masters remain unconstructed. +- [x] T008 [US1] Record owner direction selection in `dist/private/insonic/brief.json` and `specs/070-insonic-brand/spec.md` (FR-001, FR-003). Owner selects A1 without its crossing bar, Glacial blue, and retention of all three forms with stronger tiny-size stems. Exact Gate 1 remains pending. +- [x] T009 [US1] Construct candidate `dist/private/insonic/gate-1/build/mk_paths.py` and `brand.json` with allowed primitives and validate glyphs and explicit source contract (FR-003, FR-004). Full preserves selected A1 paths; Reduced strengthens stems from 125 to 160 units. Fifteen glyph checks pass with zero warnings and failures. +- [x] T010 [US1] Qualify palette, framing, topology, canonical renderer, 32 proofs and comparison artifacts in `dist/private/insonic/gate-1/` (FR-003, FR-007). All 32 repeated production renders match exactly. The packet binds 128 comparison artifacts, measured palette evidence and Windows Node 24.11.0/resvg 2.6.2/Pillow 12.3.0 settings. Owner approval remains pending. +- [x] T011 [US1] Request exact Gate 1 review, record owner wording/digests, and atomically promote approved bytes to `brands/insonic/` (FR-004). Owner states "Approved as provided" for packet `a0a00d7abac8f5b5bc31fe7183bbdd6b48d4ec37e744271581a0771e963a6da5`; promotion preserves helper bytes and exact identity snapshot. All 32 regenerated proofs and 128 comparison hashes match approval. + +## Phase 4: User Story 2, Fundamentals and Verified Kit + +**Goal**: Approve the assembled identity and receive usable verified assets. + +**Independent Test**: Current-source private packet and exact messages pass validation, then the final kit reports zero mandatory problems. + +- [x] T012 [US2] Propose and record independent exact message roles/uses in `brands/insonic/brand.json` and `dist/private/insonic/brief.json`; retain absent/unresolved states (FR-006). Owner states "Everything is looking perfect. CONTINUE" in response to the exact proposals and role/use questions. All three core roles carry independent wording, uses and owner evidence; social copy has exact layout and one-line text. Canonical identity snapshot remains unchanged. +- [x] T013 [US2] Generate private wide/stacked lockups, wordmark, palettes, typography and application specimen in `dist/private/insonic/gate-2/` from promoted source (FR-005, FR-007). Production generator emits twenty SVGs and platform assets. Identity overview, functional-cue sheet and synthetic developer application are rendered and visually inspected. Copy and formal Gate 2 remain pending. +- [x] T014 [US2] Assemble distinct social SVG/PNG with exact decided copy/layout and generate checksummed `dist/private/insonic/gate-2/gate-2-packet.json` (FR-005, FR-006). Canonical social pixels match the reviewed proposal exactly. The formal packet passes the existing authoring validator and binds eighteen review assets, production derivative manifest, current source continuity, generated typography and voice guidance. After owner feedback, correct the upstream specimen header, regenerate its SVG/PNG, inspect and measure alignment, and refresh the packet digest. Assembled Gate 2 remains pending. +- [x] T015 [US2] Validate the source-bound packet, inspect its rendered assets, and request Gate 2; record exact ledger and social-image digests in `brands/insonic/brand.json` (FR-004, FR-005). Owner states "approved. CONTINUE" for the corrected eighteen-asset packet `fc80f4ef0c21c88a9166a6044838b081b005d3aa8993402e6f32084c209d9acf`. All eight surfaces and exact social SVG/PNG digests are recorded; canonical Gate 1 source binding remains unchanged. +- [x] T016 [US2] Compile `dist/insonic/` through `scripts/build_all.py insonic`; run glyph, kit, render and pagination gates and inspect contact sheets (FR-007, FR-008). Final compiler 3.0.1 build reports zero mandatory problems and fifteen glyph checks with zero warnings/failures. PDF, logo and all page-QC sheets were inspected; raw WordPress template screenshots are fragment previews, not installed WordPress runtime evidence. +- [x] T017 [US2] Scan source/deliverable text for encoding, line endings, and mojibake; verify illustrative-only public specimen data in `dist/insonic/` (FR-010). All 186 source/spec/kit text files pass strict UTF-8, no BOM, LF and mojibake checks; 341 PNGs decode. Exact approved guidance, messages and ledger reach the final kit. Application examples retain synthetic data. + +## Phase 5: User Story 3, Official Showcase + +**Goal**: Publish the verified identity and matching downloadable kit through the official site. + +**Independent Test**: Live brand routes and archive checksums match the approved release after definitive CI. + +- [x] T018 [US3] Add insonic to `skill/templates/interface_contract.py`, publication audit/test inventories, `scripts/audit_identity_continuity.py`, and `site/tests/site.test.mjs`; preserve client archive eligibility (FR-009). +- [x] T019 [US3] Add canonical proof export and verified `dist/insonic/` artifact inventory to `.github/workflows/build.yml` (FR-004, FR-011). +- [x] T020 [US3] Synchronize release metadata in `skill/SKILL.md`, `site/package.json`, changelogs, release impact and release-contract tests (FR-012). Compiler/site 3.0.1 and insonic 1.0.0 are explicit; canon and adapters remain pinned. Twenty-seven release-contract tests pass. +- [x] T021 [US3] Build/audit `site/` from verified kit output and check catalog, guideline, registry, conformance, metadata, download and private-content exclusion behavior (FR-009, FR-010). Site export, semantic publication audit and complete browser suite pass: 130 HTML routes, 260 route/viewport cases and 90 visual cases across two themes, with zero WCAG 2.1 AA violations. +- [x] T022 [US3] Run current documented aggregate production/contract/registry/site/publication gates and record results in `specs/070-insonic-brand/verification.md` (FR-011, FR-012). All twelve builds, 31 contract-check commands (including 99 pipeline tests), registry consumer installation, guide delivery, eleven archive contracts, public documentation and site audits pass. Owner-authorized fixture remediation passes seven transport tests, frozen install, zero-vulnerability npm audit and both pinned runtime pairs. Required authoritative CI is part of T023. +- PR review follow-up to T022: Add a dependency-resolving construction launcher while preserving canonical helper bytes. Four actual execution/hash/validation regressions pass; final compilation and authoritative CI must include the launcher. +- [ ] T023 [US3] Complete the reviewed change and tagged CI release workflow; verify live official routes and archive checksums in `specs/070-insonic-brand/verification.md` (FR-012). + +## Final Phase: Reconciliation + +- [x] T024 Reconcile `specs/070-insonic-brand/` against final source, creative decisions, evidence, and any remaining deviations (FR-001 through FR-012). Both approvals and the bounded dependency-remediation expansion are recorded. All requirements have task coverage; only official publication T023 remains pending. +- [x] T025 Check repository hygiene and `git diff --check`; preserve source-only changes and keep generated `dist/` output untracked (FR-010, FR-011). Only approved source, compiler/tests, release/site inventories, test-fixture source and Spec Kit records enter Git. Generated output and feature state stay ignored; final encoding and diff checks pass. + +## Dependencies and Execution Order + +Setup and analysis precede construction. US1 completes Gate 1 before US2 creates reviewed derivatives. US2 completes Gate 2 before final compilation and US3 public projection. T023 follows definitive aggregate CI and the release workflow. Discovery answers can arrive while unrelated research continues, but missing approval never unblocks dependent work. + +## Parallel Opportunities + +Read-only integration research and source-product research can proceed independently. After approved source exists, artifact families may be generated through the existing pipeline. Owner reviews and mutations remain sequential; no parallel agents are needed for source editing. + +## Implementation Strategy + +First deliver concrete direction choices, then the qualified Gate 1 packet, then assembled fundamentals, then the verified portable kit and official publication. The first reviewable increment is a direction sheet, not a publishable identity. Pending decisions remain visibly pending. diff --git a/specs/070-insonic-brand/verification.md b/specs/070-insonic-brand/verification.md new file mode 100644 index 0000000..35693bc --- /dev/null +++ b/specs/070-insonic-brand/verification.md @@ -0,0 +1,70 @@ +# S070 Verification + +Date: 2026-10-03. Status: both creative gates approved; final verification and official publication are in progress. + +## Historical Review-Stage Evidence + +- Repository BrandBuilder 3.0.0 matches the latest published v3.0.0 release. +- Local repository and fetched main were at `99245f87b4a1ceca8ecf35b0245dd0eb6916f4b4` before branch creation. +- The private tool probe reports full capability tier at `dist/private/insonic/qc/probe.json`. +- The initial private authoring brief passed the existing validator, and initial brief/research text passed UTF-8 without BOM, LF and mojibake sanity checks. +- Installed Spec Kit template resolution, feature state, planning setup and task setup were executed. Scope clarification scan found no unanswered workflow/architecture requirement; audience and creative direction remain interactive brief decisions. +- Cross-artifact analysis: 12 requirements, 25 tasks, 100 percent requirement coverage, zero critical issues, zero duplicated requirements, and no unmapped tasks. Creative choices remain pending in the brief rather than being treated as specification gaps or approvals. +- Three exploratory directions were rendered and visually inspected in `dist/private/insonic/directions/direction-sheet.png`: Signal lattice, Voice continuum, and Voiced monogram. Each shares one provisional palette for comparison, with tiny exploratory previews and light-surface specimens. These are not canonical proofs or final favicon masters. +- Coloraide measured the candidate Sea glass, Glacial blue, and Mineral slate pairs in `dist/private/insonic/directions/palette-evidence.json`; tested normal-text and white-on-action pairs exceed the AA floor. Full identity palette, color-vision and rendered-role qualification is still pending. +- After the owner selected A and identified its messy crossing bar, refinement 02 was rendered and visually inspected. A1 removes the crossing element while preserving the three original voice forms. A2 moves the connection to a common terminal baseline. The sheet includes original A, dark/light treatments and tiny exploratory previews. Glyphkit command and contained-bound checks passed; these remain concepts without production Full/Reduced qualification or Gate 1 approval. +- After the owner selected A1 and requested palette comparisons, palette study 03 was rendered and visually inspected with identical selected A1 path arrays in Sea glass, Glacial blue, and Mineral slate on dark and light backgrounds. Palette text/mark ink pairs pass measured contrast checks; the production palette choice remains pending. +- Owner then selects Glacial blue and "Keep all three; strengthen them for tiny sizes". The private production candidate retains the selected Full paths exactly and widens Reduced stems from 125 to 160 units. The source contract and glyphkit helper validate; fifteen glyph checks report zero warnings and zero failures. +- Gate 1 packet `insonic-a1-glacial-20261003` binds exact source, topology, framing, qualified palette and the Windows production renderer (Node 24.11.0, resvg 2.6.2, Pillow 12.3.0). Two independent production render passes produce byte-identical PNGs for all 32 coordinates. Each proof binds side-by-side, overlay, silhouette-XOR and color-difference evidence, totaling 128 comparison artifacts. All half-strength masks preserve three components and zero holes. The raw lower-threshold comparison masks retain their faint antialias speck measurements rather than being represented as exact structural counts. +- Coloraide qualifies the complete governed palette's sRGB/OKLCH roles, dark/light surface combinations, monochrome treatments and protan/deutan/tritan simulations. A failing proposed light hover ground was lightened to `#E6EEF3` without changing the selected blue pair. Rendered-color checks preserve observed Lanczos edge overshoot and independently check exact SVG fills, solid interiors outside its three-pixel support band, and nearest ink samples at tiny sizes. The actual production proofs are shown without recoloring. +- Review image and both full proof sheets were visually inspected. An independent packet audit confirms all 178 bound files and digests, source snapshot, palette qualification, renderer settings and complete proof coordinates. Final candidate packet SHA-256 is `a0a00d7abac8f5b5bc31fe7183bbdd6b48d4ec37e744271581a0771e963a6da5`. +- The owner states "Approved as provided" for that exact packet. `promote_identity.py` validates the approved canonical record and atomically installs three source files under `brands/insonic/`. The construction helper retains SHA-256 `c92e3bb3f8f55c80a4dd6662397f31e8819a7f7aeeaf6ca2ec7fba1171524daf`; identity snapshot remains equal to the reviewed candidate. Approval metadata is explicit, with exact owner wording, date, scope and proposal digest. The unmodified review candidate remains private. +- Production-source validation passes in the private staged kit with local house fonts. The continuity pipeline regenerates all 32 approved PNGs and all 128 comparison artifacts with exact approved hashes. `gen_logo.py` emits twenty SVGs, rasters and categorized platform icon suites from that approved source. Publication remains disabled through pending Gate 2. +- Owner selects the developer audience and slogan direction `Voice, with memory.`. Source-derived wide/stacked lockups and wordmark, formal palette, house typography, functional cues and a synthetic developer workspace are rendered and visually inspected. The twelve proposed dark/light cue combinations exceed measured normal-text AA; labels and symbols accompany state colors. The illustrative application covers the intended ingestion, storage, normalization, transcription, graph, speaker-recognition and replication scope without using personal data or claiming implemented behavior. +- The proposed social image is distinct from both lockups. Its initial centered-single-line preset crowded the wordmark, so the reviewed draft uses the existing classic slogan-only layout with one centered slogan line and no description. No generator code or approved logo configuration changed. Exact social composition, reusable copy roles and public uses remain pending; this is a draft preview, not the validated final Gate 2 packet. Draft manifest SHA-256 is `73c821615bfaa78948b572136cc318ad1c152e2cd44d19ff8e7c410d62a75d1a`. +- The owner states "Everything is looking perfect. CONTINUE" in response to the complete preview and exact copy-role questions. Approved message roles, intended uses and exact social text/layout are recorded in `brands/insonic/brand.json` and the private brief. Functional cue validation covers every declared dark/light surface. Updating non-identity copy metadata preserves the exact Gate 1 snapshot, derivative configuration digest and canonical source binding. +- The production pipeline regenerates the canonical social SVG and PNG, outlined type specimen and measured color roles. The social PNG's pixels equal the reviewed proposal exactly. All 32 logo proofs and comparison artifacts retain their approved hashes; every generated logo derivative passes its recorded SHA-256 check. Both the canonical social image and generated type specimen were visually inspected. +- Artifact sanity checking exposed platform-default CRLF output in `build_specimen.py`. The upstream generator now writes UTF-8 without BOM and explicit LF using the Python 3.8-compatible file API. Regenerated outlined typography passes the native Windows text check; the correction is documented in `brands/insonic/build/README.md`. +- The private `authoring_brief.py` Gate 2 validator passes for eighteen bound review assets, complete required roles, independent message records, separate social/wide/stacked compositions and the current canonical source digest. Following the owner-requested specimen correction, current packet SHA-256 is `fc80f4ef0c21c88a9166a6044838b081b005d3aa8993402e6f32084c209d9acf`; production derivative manifest SHA-256 remains `5c83f9a70847e9a7f4f9739bf9556db98d34f40a46e313050981118173ee94dd`. Canonical social SVG SHA-256 remains `563afab6729e12f70c55595b56f9c6a1a23b1a6c3fb46a03d414a01ce079b63f`, and PNG SHA-256 remains `9d59c9fe29d394b56a739a1c9cfc9c58c7f7c2d39d5169ab6ef88483412e1999`. The earlier `a3eee3739a5b7a2c45bd44b6d0d652bda680cd0234c291c22be6acbe8901f989` packet is superseded. Assembled approval remains pending. +- Owner review identifies the specimen's mark above the name. The upstream generator now measures font and mark ink bounds and aligns their centers in one header row. The corrected SVG and PNG were regenerated and visually inspected. Independent raster measurements place both vertical ink centers at 150.5 pixels, with zero difference. Five targeted regression tests pass, including asymmetric mark geometry, a name with descenders, source-exact embedding, supplied horizontal lockups, and raster drift detection. The actual specimen verifier reports zero problems; current source continuity and all eighteen packet assets validate; fifteen glyph checks report zero warnings and zero failures. Approved source, logo derivatives and canonical social bytes remain unchanged. + +## Specimen Correction Evidence Before Gate 2 + +Five targeted outlined-specimen regression tests pass. Final sanity checking passes for 135 authored/source/generated text files, all preview PNGs decode, the 25 task IDs remain ordered, and `git diff --check` passes. An independent file-hash audit confirms all eighteen bound Gate 2 assets and the revised packet digest. After inspecting the correction, the owner states "That looks much better"; the corrected layout is retained and complete assembled approval remains pending. + +## Approved Build and Integration + +- Owner states "approved. CONTINUE" for the revised eighteen-asset packet SHA-256 `fc80f4ef0c21c88a9166a6044838b081b005d3aa8993402e6f32084c209d9acf`. Source records bind the approved derivative manifest, all eight publication surfaces and exact canonical social SVG/PNG. Gate 1 canonical binding is unchanged. +- The first complete insonic build passes: fifteen glyph checks with zero warnings/failures, zero kit verification problems, zero affiliation and image-QC problems, zero PDF-QC problems and zero split pagination elements. The logo and PDF contact sheets were visually inspected. Release compilation under synchronized BrandBuilder 3.0.1 is in progress. +- Issue #319 tracks the official publication slice. Source/publication inventories and Windows canonical proof exports include insonic. The site inventory is twelve brands, with nine owned release archives and the existing three hosted-only client kits. Twenty-seven synchronized release-contract tests pass. + +## Final Local Delivery Evidence + +- Final `scripts/build_all.py insonic` under BrandBuilder 3.0.1 passes with zero kit, affiliation, image-QC and PDF-QC problems, zero split pagination elements, and fifteen glyph checks with zero warnings/failures. All generated logo, PDF and page-QC sheets were inspected. Raw WordPress template fragment previews do not constitute installed WordPress runtime verification. +- The complete eight-page PDF and portable guide carry the exact approved lowercase naming, logo usage and palette guidance through the existing `guidance` fields. Source mapping corrects an omitted narrative projection without changing approved wording, Gate 1 source binding or exact social composition. +- Strict deliverable checking passes for 186 source/spec/generated-kit text files: UTF-8, no BOM, LF and no mojibake. All 341 generated PNGs decode. Final kit messages, guidance and approval ledger equal approved source records. +- Brand essentials delivery passes across all twelve production guides. Registry delivery validates twelve catalogs and local font bundles, then installs twenty-seven UI items, theme tokens and bundled fonts into a clean consumer and builds it successfully. +- All 31 shared contract-check commands pass, including all 99 pipeline tests, identity continuity, rendered alignment, simultaneous QC capture isolation, source/publication contracts, documentation audits and Markdown checks. Declared optional skips belong to explicitly simulated core-tier fixtures; the production insonic build completes its mandatory checks. +- Required fixture `npm audit --prefix scripts/wordpress-runtime --audit-level=moderate` fails with four high-severity dependency findings for GHSA-ch52-4w7c-c8xp. The installed `@wordpress/env` 11.16.0 and `http-cache-semantics` 4.2.0 match current npm latest versions. The [reviewed advisory](https://github.com/advisories/GHSA-ch52-4w7c-c8xp) lists no patched release; [upstream remediation PR 58](https://github.com/kornelski/http-cache-semantics/pull/58) remains open. No audit suppression or release bypass has been added. WordPress runtime checks after that audit are not yet performed. + +- Owner authorizes remediation with "Yes, remediate the test dependency". A fixture-only built-in-fetch transport removes the affected cache packages from the dependency graph; frozen `npm ci --ignore-scripts` and the unchanged moderate-threshold audit pass with zero vulnerabilities. Seven transport regressions pass, including repeated requests without replayed cookies/cache, redirects, exact streaming bytes/progress, HTTP/truncation failures, request cancellation, actual installed runner resolution, and installed wp-env ZIP download/extraction. +- Both pinned runtime pairs pass: WordPress 6.9.9/PHP 8.2 and WordPress 7.1.2/PHP 8.3. Checks cover theme ZIP installation, native patterns, update/rollback, saved overrides, local fonts, non-root asset URL composition, live asset retrieval, keyboard focus and narrow/wide browser AA probes. Both fixture environments were stopped after verification. No audit suppression or release bypass was introduced. +- All twelve production distributions pass final 3.0.1 builds using their approved Node 24.11.0 or Node 26.5.0 renderer groups. Production PDF contact sheets were inspected. Existing imported identities retain their explicit permitted provenance warnings and zero glyph failures; insonic reports no glyph warnings. +- Site preparation, type checking, Next.js static export, README-link checks and origin/payload tests pass. Browser verification has completed 260 route/viewport cases and is continuing through the complete visual and interaction suite. +- Release packaging and generated notes validate eleven local archives: the skill, portable distribution and nine owned kits. The three client archives remain hosted candidates. Site registry inventory and prepared public documentation audits pass. +- Publication content/semantic audit passes with twelve brands, twelve governed kit markers, twelve site markers, twelve hosted packages and 2,855 public files. The local `dist/` also holds development runtimes and caches, so this check uses byte-preserving copies of the twelve production kits in a clean ignored staging directory. The raw mixed development directory is not a publication artifact; no audit rules were relaxed. +- Final extended encoding checks pass for 199 source/spec/kit/fixture text files and all 341 insonic PNGs. `git diff --check` and amended publication workflow tests pass (28 tests). The new workflow contract enforces frozen install, transport regressions and the unchanged npm audit before runtime verification. + +## Local Review and Publication Handoff + +The complete site suite passes: 130 HTML routes at desktop/mobile widths, 260 route/viewport cases and 90 visual cases across two themes, with zero WCAG 2.1 AA violations. The rendered insonic desktop/mobile Brand essentials pages and asset catalog were inspected. Local production, contract, registry, archive, publication, dependency and site gates are green. + +Final non-destructive Spec Kit analysis finds no constitution conflicts, duplicated requirements, unresolved specification placeholders or unmapped tasks. All twelve functional requirements and five success criteria map to the twenty-five chronological tasks. The owner-authorized test-dependency remediation maps to the existing aggregate validation requirement and is recorded in specification, plan, tests and CI. + +Code review checks source-only boundaries, unchanged sibling geometry, exact approval bindings, the installed HTTP transport API surface and audit preservation, Python 3.8-compatible specimen serialization, release eligibility, generated public metadata and matching archive identities. No unresolved code-review finding remains. Authoritative CI, reviewed merge, tagged release and live official-site verification remain pending. Local archives are verification inputs only and will not be uploaded as official release assets. + +## Publication PR Review Correction + +PR #320 automated review identifies that directly executing the canonical helper cannot find its glyphkit dependency. Preserve the immutable approved `mk_paths.py` (SHA-256 `c92e3bb3f8f55c80a4dd6662397f31e8819a7f7aeeaf6ca2ec7fba1171524daf`) and supply the library through a separate construction launcher. Four regression checks pass: repository execution from an unrelated directory, staged standalone-kit execution with an explicit installed library, actionable missing-library failure, and unchanged helper bytes plus module glyph validation. Both successful executions reproduce the exact approved grid, Full and Reduced path arrays. The first PR CI attempt is deliberately canceled before source revision, with proof and Python compatibility jobs already passing; the final reviewed revision requires a fresh complete run. + +The corrected insonic kit rebuild passes all mandatory stages, including zero verify, affiliation, image-QC, PDF-QC and pagination problems, and fifteen glyph checks with zero warnings/failures. The canonical approval record remains unchanged. Publication workflow contracts (28 tests), source public-documentation audit and Markdown checks also pass after adding the launcher to both Python compatibility and contract CI jobs.