ace-hitl-contract 0.2.1 → 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 +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +13 -0
- data/lib/ace/hitl/contract/examples/ingress-checkpoints.json +8 -4
- data/lib/ace/hitl/contract/examples/operator-question.json +13 -0
- data/lib/ace/hitl/contract/operator-question.v1.schema.json +63 -0
- data/lib/ace/hitl/contract/operator_envelope.rb +43 -0
- data/lib/ace/hitl/contract/request_envelope.rb +42 -0
- data/lib/ace/hitl/contract/version.rb +1 -1
- data/lib/ace/hitl/contract.rb +2 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 688f70e592148bdab2e4e948f65d8d181874991857eef5ece11cb21efacd2d5c
|
|
4
|
+
data.tar.gz: bbde12cb9d41744dc25e876697ffd94002105db82f0e2f912e7f4d5ee55fc144
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c2984a72c8276b92e8a1961b95a9cd8f4de8b9dba18d4c7255cbbe7e64a16e26ac303be61c67186bd77f0d5a748b278b0e9773077bcb6eb68c0b4019378503fb
|
|
7
|
+
data.tar.gz: 66decc3f22461f6caf6eb6ee07f2646ab0ba12c46fb9505b33dc0b230811eb099d56eae4f2b3a35b6deec7193053630386c4a521ce94da6073b09a15eee2a7c0
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
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
|
+
|
|
5
15
|
## [0.2.1] - 2026-10-10
|
|
6
16
|
|
|
7
17
|
### Fixed
|
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
|
+
}
|
|
@@ -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
|
data/lib/ace/hitl/contract.rb
CHANGED
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.
|
|
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
|