ace-hitl-contract 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 759e3f37e3bd513e374fbcd315d630178d48978c6e8b07da31d0b94b4a16d7e4
4
- data.tar.gz: 4568504b1fc12a0e39e36b7f440e523588cd758d7eb0f750fa6ff4226d2008b5
3
+ metadata.gz: 688f70e592148bdab2e4e948f65d8d181874991857eef5ece11cb21efacd2d5c
4
+ data.tar.gz: bbde12cb9d41744dc25e876697ffd94002105db82f0e2f912e7f4d5ee55fc144
5
5
  SHA512:
6
- metadata.gz: 77635aadf48b9cb6f79039688b61e440b0977b243d3dc846e138478201c8b4c76066a3139488d2de2d91de0c75bcf975134719ed6bf2cd57e00e2842c356b090
7
- data.tar.gz: 912c8dfb1cd803292bac777a18f4ce81b502240e359b1a606575d9c7f99ce2544ef800207d490de4fea246c304dc7c7d2adf37f7f256991d52c45028f4cda4a0
6
+ metadata.gz: c2984a72c8276b92e8a1961b95a9cd8f4de8b9dba18d4c7255cbbe7e64a16e26ac303be61c67186bd77f0d5a748b278b0e9773077bcb6eb68c0b4019378503fb
7
+ data.tar.gz: 66decc3f22461f6caf6eb6ee07f2646ab0ba12c46fb9505b33dc0b230811eb099d56eae4f2b3a35b6deec7193053630386c4a521ce94da6073b09a15eee2a7c0
data/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.3.0] - 2026-10-16
6
+
7
+ ### Added
8
+
9
+ - Add a closed operator-question envelope and tagged request union without assignment/attempt claims; ship its schema and example. Keep managed request validation unchanged.
10
+
11
+ ### Changed
12
+
13
+ - Include exact project identity in shared ingress checkpoint examples for project-scoped proposal authorization.
14
+
15
+ ## [0.2.1] - 2026-10-10
16
+
17
+ ### Fixed
18
+
19
+ - Preserve exact protected launch attempt identifiers in managed envelopes without widening assignment IDs or expected binding correlation.
20
+
5
21
  ## [0.2.0] - 2026-10-05
6
22
 
7
23
  ### Added
data/README.md CHANGED
@@ -53,3 +53,16 @@ configuration `environment.overrides.ACE_HITL_SCHEMA_PYTHON`; run the absolute
53
53
  test file with `bin/ace-test ace-hitl-contract ... --config-path /absolute/fixture.yml`.
54
54
  The default package run skips this external validator check; no runtime gem
55
55
  dependency or production configuration is added.
56
+
57
+ `RequestEnvelope.load` is the closed union of the unchanged managed schema and
58
+ `OperatorEnvelope` (`ace.hitl.operator-question/v1`). The operator variant fixes
59
+ `context: operator-question` and `kind: question`, names the real requester UID,
60
+ project, immutable incarnation/correlation and nonsecret question/input digests.
61
+ It rejects assignment, attempt, reverse, effects, OTP and publication fields.
62
+ `RequestEnvelope.from_facts` also checks the owner's exact attribution, project,
63
+ classification and question payload binding. Its operator schema and example
64
+ ship as `operator-question.v1.schema.json` and `examples/operator-question.json`.
65
+ Structural validation grants no identity or execution authority; generic HITL
66
+ checks the kernel principal and installed grants. A future command context needs
67
+ its own canonical provenance and validator; an operator question cannot stand in
68
+ for command execution or publisher OTP.
@@ -9,7 +9,8 @@
9
9
  "healthy": false,
10
10
  "drained": false,
11
11
  "checkpoint": null,
12
- "unresolved": []
12
+ "unresolved": [],
13
+ "project": "ace"
13
14
  },
14
15
  {
15
16
  "schema": "ace.hitl.hermes.ingress-checkpoint/v1",
@@ -23,7 +24,8 @@
23
24
  "through": "2026-10-05T10:00:00Z",
24
25
  "sequence": 4
25
26
  },
26
- "unresolved": []
27
+ "unresolved": [],
28
+ "project": "ace"
27
29
  },
28
30
  {
29
31
  "schema": "ace.hitl.hermes.ingress-checkpoint/v1",
@@ -42,9 +44,11 @@
42
44
  "request": "hitl-abcdef1234",
43
45
  "revision": "def456",
44
46
  "received_at": "2026-10-05T09:59:59Z",
45
- "status": "unresolved"
47
+ "status": "unresolved",
48
+ "project": "ace"
46
49
  }
47
- ]
50
+ ],
51
+ "project": "ace"
48
52
  }
49
53
  ]
50
54
  }
@@ -0,0 +1,13 @@
1
+ {
2
+ "schema": "ace.hitl.operator-question/v1",
3
+ "context": "operator-question",
4
+ "request_id": "operator-abcdef",
5
+ "request_incarnation": "0123456789abcdef",
6
+ "project": "lab-config",
7
+ "requester": "mo",
8
+ "requester_uid": 1000,
9
+ "correlation_id": "operator-abcdef",
10
+ "kind": "question",
11
+ "payload_sha256": "0cd45088a48787af9eb875a2de1912f5146371da61e69f44f1843ff7a8eb0cb9",
12
+ "input_sha256": "daad7a6851f6289cae1d2f2088629fd9d00133ad517f0e6ef697c2f36b8dc62d"
13
+ }
@@ -20,6 +20,7 @@ module Ace
20
20
  OPTIONAL = %w[payload_sha256 message effect].freeze
21
21
  TOKEN = /\A[A-Za-z0-9][A-Za-z0-9._:-]{0,127}\z/
22
22
  COMPACT = /\A[0-9a-z][0-9a-z]{4,63}\z/
23
+ PROTECTED_ATTEMPT = /\Alaunch-[0-9a-f]{24}\z/
23
24
  DIGEST = /\A[0-9a-f]{64}\z/
24
25
  KINDS = %w[text choice confirm review question decision verification otp proposal].freeze
25
26
  MESSAGE_SCHEMA = "ace.hitl.hermes.message/v1"
@@ -39,7 +40,8 @@ module Ace
39
40
  raise InvalidEnvelope, "unsupported managed envelope version" unless data["schema"] == SCHEMA
40
41
  %w[request_id request_incarnation project requester correlation_id].each { |key| token!(data[key], key) }
41
42
  %w[assignment_id attempt_id].each do |key|
42
- raise InvalidEnvelope, "invalid #{key}" unless data[key].is_a?(String) && COMPACT.match?(data[key])
43
+ raise InvalidEnvelope, "invalid #{key}" unless data[key].is_a?(String) &&
44
+ (COMPACT.match?(data[key]) || (key == "attempt_id" && PROTECTED_ATTEMPT.match?(data[key])))
43
45
  end
44
46
  raise InvalidEnvelope, "unsupported managed request kind" unless KINDS.include?(data["kind"])
45
47
  reverse!(data["reverse"])
@@ -0,0 +1,63 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://ace.tools/schemas/hitl/operator-question.v1.schema.json",
4
+ "title": "Unassigned operator question; structure is not authority",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "schema",
9
+ "context",
10
+ "request_id",
11
+ "project",
12
+ "requester",
13
+ "correlation_id",
14
+ "request_incarnation",
15
+ "requester_uid",
16
+ "kind",
17
+ "payload_sha256",
18
+ "input_sha256"
19
+ ],
20
+ "properties": {
21
+ "schema": {
22
+ "const": "ace.hitl.operator-question/v1"
23
+ },
24
+ "context": {
25
+ "const": "operator-question"
26
+ },
27
+ "request_id": {
28
+ "type": "string",
29
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"
30
+ },
31
+ "project": {
32
+ "type": "string",
33
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"
34
+ },
35
+ "requester": {
36
+ "type": "string",
37
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"
38
+ },
39
+ "correlation_id": {
40
+ "type": "string",
41
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"
42
+ },
43
+ "request_incarnation": {
44
+ "type": "string",
45
+ "pattern": "^[0-9a-f]{16}$"
46
+ },
47
+ "requester_uid": {
48
+ "type": "integer",
49
+ "minimum": 0
50
+ },
51
+ "kind": {
52
+ "const": "question"
53
+ },
54
+ "payload_sha256": {
55
+ "type": "string",
56
+ "pattern": "^[0-9a-f]{64}$"
57
+ },
58
+ "input_sha256": {
59
+ "type": "string",
60
+ "pattern": "^[0-9a-f]{64}$"
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ require "json"
3
+ require_relative "managed_envelope"
4
+
5
+ module Ace
6
+ module Hitl
7
+ module Contract
8
+ # A question has a real local requester and project, but no execution or
9
+ # effect authority. Its closed schema cannot carry managed/OTP grants.
10
+ class OperatorEnvelope
11
+ SCHEMA = "ace.hitl.operator-question/v1"
12
+ FIELDS = %w[schema context request_id request_incarnation project requester requester_uid
13
+ correlation_id kind payload_sha256 input_sha256].freeze
14
+
15
+ def self.load(value, expected: {})
16
+ data = value.is_a?(String) ? JSON.parse(value) : JSON.parse(JSON.generate(value))
17
+ unless data.is_a?(Hash) && data.keys.sort == FIELDS.sort && data["schema"] == SCHEMA &&
18
+ data["context"] == "operator-question" && data["kind"] == "question"
19
+ raise InvalidEnvelope, "invalid closed operator question envelope"
20
+ end
21
+ %w[request_id project requester correlation_id].each do |key|
22
+ unless data[key].is_a?(String) && ManagedEnvelope::TOKEN.match?(data[key])
23
+ raise InvalidEnvelope, "invalid operator #{key}"
24
+ end
25
+ end
26
+ unless data["request_incarnation"].is_a?(String) && data["request_incarnation"].match?(/\A[0-9a-f]{16}\z/) &&
27
+ data["requester_uid"].is_a?(Integer) && data["requester_uid"] >= 0 &&
28
+ data["correlation_id"] == data["request_id"] &&
29
+ %w[payload_sha256 input_sha256].all? { |key|
30
+ data[key].is_a?(String) && ManagedEnvelope::DIGEST.match?(data[key]) }
31
+ raise InvalidEnvelope, "invalid operator question identity or input digest"
32
+ end
33
+ expected.each do |key, wanted|
34
+ raise InvalidEnvelope, "operator #{key} differs from authoritative binding" unless data[key.to_s] == wanted
35
+ end
36
+ data
37
+ rescue JSON::ParserError, JSON::GeneratorError, TypeError => e
38
+ raise InvalidEnvelope, "invalid operator envelope JSON (#{e.class})"
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ require_relative "operator_envelope"
3
+
4
+ module Ace
5
+ module Hitl
6
+ module Contract
7
+ # Explicit tagged union; unknown schemas never become an operator context.
8
+ module RequestEnvelope
9
+ VALIDATORS = {ManagedEnvelope::SCHEMA => ManagedEnvelope, OperatorEnvelope::SCHEMA => OperatorEnvelope}.freeze
10
+ module_function
11
+ def load(value, expected: {})
12
+ data = value.is_a?(String) ? JSON.parse(value) : value
13
+ validator = data.is_a?(Hash) && VALIDATORS[data["schema"]]
14
+ raise InvalidEnvelope, "unsupported request context schema" unless validator
15
+ validator.load(data, expected: expected)
16
+ rescue JSON::ParserError
17
+ raise InvalidEnvelope, "invalid request context JSON"
18
+ end
19
+
20
+ def from_facts(facts)
21
+ envelope = load(facts.fetch("envelope"))
22
+ expected = {request_id: facts["id"], correlation_id: facts["id"], project: facts["project"],
23
+ requester: facts["requester"], kind: facts["kind"]}
24
+ if envelope["schema"] == ManagedEnvelope::SCHEMA
25
+ expected.merge!(assignment_id: facts["assignment"], attempt_id: facts["attempt"])
26
+ else
27
+ expected[:requester_uid] = facts["requester_uid"]
28
+ if facts.key?("question") && (!facts["question"].is_a?(String) ||
29
+ Digest::SHA256.hexdigest(facts["question"]) != envelope.fetch("payload_sha256"))
30
+ raise InvalidEnvelope, "operator question payload differs from its binding"
31
+ end
32
+ unless facts["sensitive"] == false && !facts["effect"] && !facts["otp"] &&
33
+ !facts["assignment"] && !facts["attempt"] && !facts["publication_binding"]
34
+ raise InvalidEnvelope, "operator question facts cannot claim managed or secret authority"
35
+ end
36
+ end
37
+ load(envelope, expected: expected)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -3,7 +3,7 @@
3
3
  module Ace
4
4
  module Hitl
5
5
  module Contract
6
- VERSION = "0.2.0"
6
+ VERSION = "0.3.0"
7
7
  end
8
8
  end
9
9
  end
@@ -5,3 +5,5 @@ require_relative "contract/errors"
5
5
  require_relative "contract/ref"
6
6
  require_relative "contract/results"
7
7
  require_relative "contract/managed_envelope"
8
+
9
+ require_relative "contract/request_envelope"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-hitl-contract
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
@@ -68,9 +68,13 @@ files:
68
68
  - lib/ace/hitl/contract/examples/managed-answer.json
69
69
  - lib/ace/hitl/contract/examples/managed-otp-request.json
70
70
  - lib/ace/hitl/contract/examples/managed-request.json
71
+ - lib/ace/hitl/contract/examples/operator-question.json
71
72
  - lib/ace/hitl/contract/managed.v1.schema.json
72
73
  - lib/ace/hitl/contract/managed_envelope.rb
74
+ - lib/ace/hitl/contract/operator-question.v1.schema.json
75
+ - lib/ace/hitl/contract/operator_envelope.rb
73
76
  - lib/ace/hitl/contract/ref.rb
77
+ - lib/ace/hitl/contract/request_envelope.rb
74
78
  - lib/ace/hitl/contract/results.rb
75
79
  - lib/ace/hitl/contract/secret_gate.rb
76
80
  - lib/ace/hitl/contract/version.rb