visual_brief_guard 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +8 -0
- data/LICENSE +21 -0
- data/README.md +148 -0
- data/lib/visual_brief_guard/version.rb +3 -0
- data/lib/visual_brief_guard.rb +77 -0
- metadata +49 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 65166b9ead1264ae2cfec8acefedf80fb658910155b10953a0c81e287d8aa8e9
|
|
4
|
+
data.tar.gz: dae4ef2111c521d439d0c21a153d8da90b15f1160eb38e45e116b68daf562f7a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b9e21a04afe44bc46792d521e708a410c3f116b68b0c449a335472ebe8f2a66ca964468a0f7e55c5f77b703eefad49e0385e539553acd2007eea9f4f9627f386
|
|
7
|
+
data.tar.gz: b3e8ab26e393ff8999deb43c2d34324cae763bde38cdde8f4d86d6961be1aa7eb0f0de2d3cd6af628622bdc14328862bcbaeb05011194f2aa84dac9cbcb93196
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0 - 2026-07-29
|
|
4
|
+
|
|
5
|
+
- Validate required and allowed visual brief fields.
|
|
6
|
+
- Canonicalize nested hashes and integral floats.
|
|
7
|
+
- Export stable JSON and calculate SHA-256 fingerprints.
|
|
8
|
+
- Add tests for ordering, validation, normalization, and export behavior.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Martyn Foster
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Visual Brief Guard
|
|
2
|
+
|
|
3
|
+
Visual Brief Guard is a small Ruby library for validating structured visual
|
|
4
|
+
briefs before they are exported to JSON. It keeps schema validation,
|
|
5
|
+
canonicalization, and fingerprinting separate from any downstream image
|
|
6
|
+
generation step.
|
|
7
|
+
|
|
8
|
+
## Why deterministic briefs matter
|
|
9
|
+
|
|
10
|
+
Two briefs can describe the same creative intent but produce different JSON
|
|
11
|
+
bytes because their hash keys were inserted in a different order or because
|
|
12
|
+
one process serialized a whole number as `1.0` and another as `1`. That causes
|
|
13
|
+
avoidable cache misses, duplicate work, and noisy version-control diffs.
|
|
14
|
+
|
|
15
|
+
Visual Brief Guard converts nested hashes into a canonical form before JSON
|
|
16
|
+
serialization. Keys are converted to strings and sorted recursively, arrays
|
|
17
|
+
retain their meaningful order, and integral floats are normalized to integers.
|
|
18
|
+
The resulting JSON can be compared, versioned, or hashed consistently.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
gem "visual_brief_guard"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then run:
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
bundle install
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
require "visual_brief_guard"
|
|
36
|
+
|
|
37
|
+
brief = {
|
|
38
|
+
scene: "A ceramic mug on a clean studio surface",
|
|
39
|
+
aspect_ratio: "4:5",
|
|
40
|
+
color_palette: "warm neutrals",
|
|
41
|
+
reference_tags: ["mug", "front-view"],
|
|
42
|
+
constraints: {
|
|
43
|
+
width: 1200.0,
|
|
44
|
+
height: 1500
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
VisualBriefGuard.validate!(brief)
|
|
49
|
+
json = VisualBriefGuard.canonical_json(brief)
|
|
50
|
+
fingerprint = VisualBriefGuard.fingerprint(brief)
|
|
51
|
+
VisualBriefGuard.export(brief, "brief.json")
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The required fields are `scene`, `aspect_ratio`, `color_palette`, and
|
|
55
|
+
`reference_tags`. Optional fields are `schema_version`, `constraints`, and
|
|
56
|
+
`notes`. Unknown fields are rejected so transient data such as timestamps
|
|
57
|
+
cannot silently change an otherwise stable fingerprint.
|
|
58
|
+
|
|
59
|
+
## Validation before serialization
|
|
60
|
+
|
|
61
|
+
Validation runs before canonicalization or digest calculation. This order
|
|
62
|
+
matters because a cryptographic digest can represent invalid data just as
|
|
63
|
+
reliably as valid data. A malformed brief should therefore fail at the
|
|
64
|
+
boundary, rather than receive a fingerprint that looks trustworthy.
|
|
65
|
+
|
|
66
|
+
`validate!` raises `VisualBriefGuard::InvalidBrief` with a specific reason when
|
|
67
|
+
the input is not a hash, a required field is missing or empty,
|
|
68
|
+
`reference_tags` is not an array, or an unexpected field is present. Calling
|
|
69
|
+
code can rescue that one exception type and return the message to an editor
|
|
70
|
+
without treating implementation errors as ordinary validation failures.
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
begin
|
|
74
|
+
VisualBriefGuard.fingerprint(candidate)
|
|
75
|
+
rescue VisualBriefGuard::InvalidBrief => error
|
|
76
|
+
warn "Brief rejected: #{error.message}"
|
|
77
|
+
end
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The field policy is intentionally explicit. Silently accepting every new key
|
|
81
|
+
would allow request IDs, timestamps, or UI state to alter a digest even though
|
|
82
|
+
those values do not describe creative intent. Add a field to the allowed
|
|
83
|
+
schema only when it should be part of the durable brief.
|
|
84
|
+
|
|
85
|
+
## Canonicalization rules
|
|
86
|
+
|
|
87
|
+
Ruby hashes retain insertion order, but that order often reflects how data
|
|
88
|
+
arrived rather than what it means. Visual Brief Guard converts keys to
|
|
89
|
+
strings, sorts them at every nesting level, and emits compact JSON. Arrays
|
|
90
|
+
remain ordered because their order may be meaningful; reference priority, for
|
|
91
|
+
example, should not be rearranged automatically.
|
|
92
|
+
|
|
93
|
+
An integral float such as `1200.0` becomes `1200`. A non-integral value remains
|
|
94
|
+
a float. This small normalization prevents equivalent dimensions from
|
|
95
|
+
producing different bytes while preserving actual fractional values.
|
|
96
|
+
|
|
97
|
+
The SHA-256 fingerprint is calculated from this canonical JSON. It is useful
|
|
98
|
+
as a cache key, duplicate detector, or immutable audit reference. It is not a
|
|
99
|
+
security signature and does not prove who created or approved a brief.
|
|
100
|
+
|
|
101
|
+
## Schema evolution
|
|
102
|
+
|
|
103
|
+
Treat the optional `schema_version` field as part of the canonical data when a
|
|
104
|
+
workflow begins evolving. Adding a new required field or changing the meaning
|
|
105
|
+
of an existing field should normally increment that version. Older records can
|
|
106
|
+
then be migrated deliberately instead of being reinterpreted silently.
|
|
107
|
+
|
|
108
|
+
Defaults also need care. If a future release adds an optional lighting field,
|
|
109
|
+
decide whether an omitted value and an explicit default should share a
|
|
110
|
+
fingerprint. Normalize the default before hashing when they are semantically
|
|
111
|
+
equivalent; leave them distinct when omission carries meaning. Tests should
|
|
112
|
+
capture that decision so a later refactor cannot change it accidentally.
|
|
113
|
+
|
|
114
|
+
## Test the invariants
|
|
115
|
+
|
|
116
|
+
The most useful tests for deterministic serialization are not limited to the
|
|
117
|
+
happy path. A robust suite should verify that shuffled key insertion order
|
|
118
|
+
produces the same digest, malformed data fails before hashing, repeated
|
|
119
|
+
canonicalization is stable, and unknown fields are either rejected or
|
|
120
|
+
explicitly added to the schema.
|
|
121
|
+
|
|
122
|
+
This gem includes Minitest coverage for those boundaries:
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
ruby -Ilib test/visual_brief_guard_test.rb
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Workflow boundary
|
|
129
|
+
|
|
130
|
+
This library stops at a validated JSON artifact. It does not call an image
|
|
131
|
+
generation API and cannot judge whether a visual result matches the creative
|
|
132
|
+
direction. That separation is intentional: deterministic input validation and
|
|
133
|
+
creative output review are different responsibilities.
|
|
134
|
+
|
|
135
|
+
After review, a person can use the exported description in a separate
|
|
136
|
+
browser-based workspace such as the
|
|
137
|
+
[Seedream 5.0 Pro AI Image Generator](https://seedream50.pro/) for text-to-image,
|
|
138
|
+
image-to-image, sketch-guided, or multi-reference exploration. This is a manual
|
|
139
|
+
handoff to an independent tool, not a programmatic integration.
|
|
140
|
+
|
|
141
|
+
Stable input does not guarantee identical generated images because downstream
|
|
142
|
+
systems may be stochastic. It does provide a dependable record of intent: if
|
|
143
|
+
two accepted briefs have the same canonical fingerprint, their validated data
|
|
144
|
+
is structurally identical.
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require "digest"
|
|
2
|
+
require "json"
|
|
3
|
+
|
|
4
|
+
require_relative "visual_brief_guard/version"
|
|
5
|
+
|
|
6
|
+
module VisualBriefGuard
|
|
7
|
+
REQUIRED_KEYS = %w[
|
|
8
|
+
scene
|
|
9
|
+
aspect_ratio
|
|
10
|
+
color_palette
|
|
11
|
+
reference_tags
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
ALLOWED_KEYS = (REQUIRED_KEYS + %w[schema_version constraints notes]).freeze
|
|
15
|
+
|
|
16
|
+
class InvalidBrief < ArgumentError; end
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def validate!(brief)
|
|
21
|
+
raise InvalidBrief, "brief must be a Hash" unless brief.is_a?(Hash)
|
|
22
|
+
|
|
23
|
+
string_keys = brief.transform_keys(&:to_s)
|
|
24
|
+
missing = REQUIRED_KEYS.reject do |key|
|
|
25
|
+
string_keys.key?(key) && !blank?(string_keys[key])
|
|
26
|
+
end
|
|
27
|
+
raise InvalidBrief, "missing required fields: #{missing.join(', ')}" unless missing.empty?
|
|
28
|
+
|
|
29
|
+
unknown = string_keys.keys - ALLOWED_KEYS
|
|
30
|
+
raise InvalidBrief, "unknown fields: #{unknown.sort.join(', ')}" unless unknown.empty?
|
|
31
|
+
|
|
32
|
+
unless string_keys["reference_tags"].is_a?(Array)
|
|
33
|
+
raise InvalidBrief, "reference_tags must be an Array"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def canonical(brief)
|
|
40
|
+
validate!(brief)
|
|
41
|
+
canonicalize(brief.transform_keys(&:to_s))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def canonical_json(brief)
|
|
45
|
+
JSON.generate(canonical(brief))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def fingerprint(brief)
|
|
49
|
+
Digest::SHA256.hexdigest(canonical_json(brief))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def export(brief, path)
|
|
53
|
+
File.write(path, "#{canonical_json(brief)}\n")
|
|
54
|
+
path
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def canonicalize(value)
|
|
58
|
+
case value
|
|
59
|
+
when Hash
|
|
60
|
+
value.transform_keys(&:to_s)
|
|
61
|
+
.sort_by { |key, _| key }
|
|
62
|
+
.to_h { |key, item| [key, canonicalize(item)] }
|
|
63
|
+
when Array
|
|
64
|
+
value.map { |item| canonicalize(item) }
|
|
65
|
+
when Float
|
|
66
|
+
value.finite? && value == value.to_i ? value.to_i : value
|
|
67
|
+
else
|
|
68
|
+
value
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
private_class_method :canonicalize
|
|
72
|
+
|
|
73
|
+
def blank?(value)
|
|
74
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
75
|
+
end
|
|
76
|
+
private_class_method :blank?
|
|
77
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: visual_brief_guard
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Martyn Foster
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: A small Ruby library for validating visual brief schemas, canonicalizing
|
|
13
|
+
nested data, exporting stable JSON, and calculating reproducible SHA-256 fingerprints.
|
|
14
|
+
email:
|
|
15
|
+
- FosterMartyn735@gmail.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- CHANGELOG.md
|
|
21
|
+
- LICENSE
|
|
22
|
+
- README.md
|
|
23
|
+
- lib/visual_brief_guard.rb
|
|
24
|
+
- lib/visual_brief_guard/version.rb
|
|
25
|
+
homepage: https://seedream50.pro/
|
|
26
|
+
licenses:
|
|
27
|
+
- MIT
|
|
28
|
+
metadata:
|
|
29
|
+
homepage_uri: https://seedream50.pro/
|
|
30
|
+
source_code_uri: https://github.com/FosterMartyn735/visual_brief_guard
|
|
31
|
+
changelog_uri: https://github.com/FosterMartyn735/visual_brief_guard/blob/main/CHANGELOG.md
|
|
32
|
+
rdoc_options: []
|
|
33
|
+
require_paths:
|
|
34
|
+
- lib
|
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 3.1.0
|
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
41
|
+
requirements:
|
|
42
|
+
- - ">="
|
|
43
|
+
- !ruby/object:Gem::Version
|
|
44
|
+
version: '0'
|
|
45
|
+
requirements: []
|
|
46
|
+
rubygems_version: 4.0.10
|
|
47
|
+
specification_version: 4
|
|
48
|
+
summary: Validate and fingerprint deterministic visual brief hashes.
|
|
49
|
+
test_files: []
|