ace-hitl 0.17.0 → 0.23.2

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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +162 -0
  3. data/README.md +60 -117
  4. data/docs/usage.md +247 -421
  5. data/handbook/workflow-instructions/hitl.wf.md +55 -6
  6. data/lib/ace/hitl/adapters/registry.rb +65 -0
  7. data/lib/ace/hitl/adapters/terminal.rb +75 -0
  8. data/lib/ace/hitl/adapters.rb +80 -0
  9. data/lib/ace/hitl/atoms/hitl_address.rb +89 -0
  10. data/lib/ace/hitl/atoms/hitl_event_text.rb +54 -0
  11. data/lib/ace/hitl/cli/commands/create.rb +81 -12
  12. data/lib/ace/hitl/cli/commands/lifecycle_command.rb +1 -7
  13. data/lib/ace/hitl/cli/commands/list.rb +22 -1
  14. data/lib/ace/hitl/cli/commands/send.rb +85 -0
  15. data/lib/ace/hitl/cli/commands/serve.rb +25 -44
  16. data/lib/ace/hitl/cli/commands/show.rb +23 -3
  17. data/lib/ace/hitl/cli/commands/update.rb +4 -0
  18. data/lib/ace/hitl/cli/commands/wait.rb +5 -0
  19. data/lib/ace/hitl/cli.rb +8 -18
  20. data/lib/ace/hitl/lifecycle/frame_serving.rb +0 -15
  21. data/lib/ace/hitl/lifecycle/kinds.rb +1 -1
  22. data/lib/ace/hitl/lifecycle/lifecycle_requests.rb +0 -36
  23. data/lib/ace/hitl/lifecycle/policy.rb +0 -44
  24. data/lib/ace/hitl/lifecycle/protocol.rb +1 -2
  25. data/lib/ace/hitl/lifecycle/service.rb +2 -3
  26. data/lib/ace/hitl/lifecycle/store.rb +11 -75
  27. data/lib/ace/hitl/lifecycle/tls_access_policy.rb +0 -14
  28. data/lib/ace/hitl/lifecycle/tls_project_scope.rb +0 -15
  29. data/lib/ace/hitl/lifecycle/tls_service.rb +2 -3
  30. data/lib/ace/hitl/lifecycle.rb +2 -4
  31. data/lib/ace/hitl/live_client.rb +11 -87
  32. data/lib/ace/hitl/models/hitl_event.rb +35 -0
  33. data/lib/ace/hitl/molecules/hitl_address_resolver.rb +80 -0
  34. data/lib/ace/hitl/molecules/hitl_config_loader.rb +22 -1
  35. data/lib/ace/hitl/molecules/hitl_creator.rb +126 -27
  36. data/lib/ace/hitl/molecules/hitl_delivery_state.rb +58 -0
  37. data/lib/ace/hitl/molecules/hitl_display_formatter.rb +3 -2
  38. data/lib/ace/hitl/molecules/hitl_loader.rb +19 -2
  39. data/lib/ace/hitl/molecules/hitl_route_loader.rb +108 -0
  40. data/lib/ace/hitl/organisms/hitl_manager.rb +73 -6
  41. data/lib/ace/hitl/organisms/hitl_sender.rb +148 -0
  42. data/lib/ace/hitl/providers/lab.rb +15 -129
  43. data/lib/ace/hitl/version.rb +1 -1
  44. data/lib/ace/hitl.rb +8 -1
  45. metadata +12 -43
  46. data/lib/ace/hitl/cli/commands/ask.rb +0 -136
  47. data/lib/ace/hitl/cli/commands/overseer_ack.rb +0 -30
  48. data/lib/ace/hitl/cli/commands/overseer_pending.rb +0 -28
  49. data/lib/ace/hitl/cli/commands/overseer_send.rb +0 -35
  50. data/lib/ace/hitl/cli/commands/proposal.rb +0 -75
  51. data/lib/ace/hitl/lifecycle/overseer.rb +0 -111
  52. data/lib/ace/hitl/lifecycle/proposals.rb +0 -297
  53. data/lib/ace/hitl/lifecycle/service_publication_binding.rb +0 -97
  54. data/lib/ace/hitl/proposals/evaluator.rb +0 -29
  55. data/lib/ace/hitl/proposals/policy.rb +0 -127
  56. data/lib/ace/hitl/providers/lab/assignment_binding.rb +0 -126
  57. data/lib/ace/hitl/providers/lab/protected_assignment_binding.rb +0 -75
  58. data/lib/ace/hitl/providers/providers.rb +0 -33
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ace/support/cli"
4
+
5
+ module Ace
6
+ module Hitl
7
+ module CLI
8
+ module Commands
9
+ class Send < Ace::Support::Cli::Command
10
+ include Ace::Support::Cli::Base
11
+
12
+ desc "Send a HITL event through the configured adapter routes"
13
+
14
+ example [
15
+ "8xhkep # Deliver one event; per-route results",
16
+ "8xhkep --scope all # Resolve across worktrees"
17
+ ]
18
+
19
+ argument :ref, required: true, desc: "HITL reference (full ID or shortcut)"
20
+
21
+ option :scope, type: :string, desc: "Scope (current, all)"
22
+ option :force, type: :boolean, desc: "Redeliver every configured route (same case ID; recipients dedup)"
23
+ option :"dry-run", type: :boolean, desc: "Show retry/skip decisions without delivering"
24
+
25
+ option :quiet, type: :boolean, aliases: %w[-q], desc: "Suppress non-essential output"
26
+ option :verbose, type: :boolean, aliases: %w[-v], desc: "Show verbose output"
27
+ option :debug, type: :boolean, aliases: %w[-d], desc: "Show debug output"
28
+
29
+ def call(ref:, **options)
30
+ scope = validate_scope(options[:scope])
31
+
32
+ sender = Organisms::HitlSender.new
33
+ report = sender.send(ref, scope: scope,
34
+ force: options[:force] == true,
35
+ dry_run: options[:"dry-run"] == true)
36
+
37
+ unless options[:quiet]
38
+ puts "HITL event #{report.event_id} sent through #{report.results.size} route(s):"
39
+ report.results.each do |result|
40
+ detail = result.detail ? " - #{result.detail}" : ""
41
+ puts " #{result.route} (#{result.adapter}): #{result.status}#{detail}"
42
+ end
43
+ report.skipped.each do |result|
44
+ detail = result.detail ? " - #{result.detail}" : ""
45
+ puts " #{result.route} (#{result.adapter}): skipped#{detail}"
46
+ end
47
+ if report.results.empty? && report.skipped.any?
48
+ puts "already delivered on all routes; nothing to retry (use --force to redeliver)"
49
+ end
50
+ end
51
+
52
+ return if options[:"dry-run"] == true
53
+
54
+ unless report.all_delivered?
55
+ raise Ace::Support::Cli::Error.new(
56
+ "HITL event '#{report.event_id}' not delivered on all routes"
57
+ )
58
+ end
59
+ rescue Ace::Hitl::Organisms::HitlManager::AmbiguousReferenceError => e
60
+ candidates = e.matches.map(&:file_path).join(", ")
61
+ raise Ace::Support::Cli::Error.new(
62
+ "HITL event '#{ref}' is ambiguous across scope '#{scope || "all"}'. Candidates: #{candidates}"
63
+ )
64
+ rescue Ace::Hitl::Adapters::Error,
65
+ Ace::Hitl::Molecules::HitlRouteLoader::NoRoutesError,
66
+ Ace::Hitl::Molecules::HitlRouteLoader::InvalidRouteError => e
67
+ raise Ace::Support::Cli::Error.new(e.message)
68
+ end
69
+
70
+ private
71
+
72
+ def validate_scope(raw_scope)
73
+ return nil if raw_scope.nil?
74
+
75
+ scope = raw_scope.to_s.strip
76
+ allowed = Ace::Hitl::Molecules::WorktreeScopeResolver::VALID_SCOPES
77
+ return scope if allowed.include?(scope)
78
+
79
+ raise Ace::Support::Cli::Error.new("Invalid scope '#{raw_scope}'. Allowed: #{allowed.join(", ")}")
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -2,40 +2,51 @@
2
2
 
3
3
  require "ace/support/cli"
4
4
  require_relative "../../lifecycle"
5
- require_relative "../../providers/providers"
5
+ require_relative "../../providers/lab"
6
6
 
7
7
  module Ace
8
8
  module Hitl
9
9
  module CLI
10
10
  module Commands
11
- # The installed scoped store boundary (spec 8wq.t.34i): serves
12
- # the private HITL store over a peer-authenticated UNIX socket.
13
- # Authorization facts come only from the trusted grants
14
- # document; the store root and socket paths come from the
15
- # deployment (options/environment), never from caller payloads.
11
+ # The operator-only local scoped store boundary: serves explicitly
12
+ # granted nonsecret operator questions over a peer-authenticated
13
+ # UNIX socket. Authorization facts come only from the trusted grants
14
+ # document; the store root and socket paths come from the deployment
15
+ # (options/environment), never from caller payloads.
16
+ #
17
+ # The installed managed-attempt authority and the host/Incus mTLS
18
+ # crossing were removed with the old authority model.
16
19
  class Serve < Ace::Support::Cli::Command
17
20
  include Ace::Support::Cli::Base
18
21
 
19
- desc "Run the authenticated HITL store boundary service (peer-credential access)"
22
+ desc "Run the operator-only HITL store boundary service (peer-credential access)"
20
23
 
21
24
  option :socket, type: :string, desc: "Boundary socket path (default: ACE_HITL_SOCKET or /run/lab/hitl.sock)"
22
25
  option :group, type: :string,
23
26
  desc: "Dedicated lifecycle group allowed to connect (required; root and lab-control are refused)"
24
27
  option :store_root, type: :string, desc: "Private store root (default: ACE_HITL_STORE_ROOT or /run/lab/hitl)"
25
- option :grants_path, type: :string, desc: "Trusted grants document (default: ACE_HITL_GRANTS_PATH or /etc/lab/ace-lab/authorization.yml)"
28
+ option :grants_path, type: :string,
29
+ desc: "Trusted grants document (default: ACE_HITL_GRANTS_PATH or /etc/lab/ace-lab/authorization.yml)"
26
30
  option :operator_only, type: :boolean,
27
31
  desc: "Serve explicitly granted nonsecret operator questions without a managed execution authority"
28
- option :repo_root, type: :string, desc: "Candidate repository for the managed attempt authority"
29
- option :tls_authority, type: :string, desc: "Serve the host/Incus mTLS crossing from this authority config (root:<service-group> 0640; loopback TCP + verified project client identity)"
32
+ option :repo_root, type: :string, desc: "(removed) managed attempt authority repository"
33
+ option :tls_authority, type: :string, desc: "(removed) host/Incus mTLS crossing authority"
30
34
 
31
35
  option :quiet, type: :boolean, aliases: %w[-q], desc: "Suppress non-essential output"
32
36
  option :verbose, type: :boolean, aliases: %w[-v], desc: "Show verbose output"
33
37
 
34
38
  def call(**options)
35
- if options[:tls_authority] && options[:operator_only]
36
- raise_cli_error("operator questions require a local kernel requester; operator-only TLS is not supported")
39
+ if options[:repo_root] || options[:tls_authority]
40
+ raise_cli_error(
41
+ "managed and TLS serve were removed with the installed attempt authority; " \
42
+ "serve supports --operator-only only"
43
+ )
44
+ end
45
+ unless options[:operator_only]
46
+ raise_cli_error(
47
+ "the installed managed attempt authority is removed; serve requires --operator-only"
48
+ )
37
49
  end
38
- return serve_tls_authority(options) if options[:tls_authority]
39
50
 
40
51
  group = dedicated_group(options)
41
52
  policy = Providers::Lab.grants_policy(grants_path: grants_path(options))
@@ -46,15 +57,9 @@ module Ace
46
57
  "fail every client endpoint verification"
47
58
  )
48
59
  end
49
- binding_policy = if options[:operator_only]
50
- raise_cli_error("operator-only serve cannot select a managed repository") if options[:repo_root]
51
- Lifecycle::OperatorOnlyBinding.new
52
- else
53
- Providers::Lab.assignment_binding(repo_root: options[:repo_root])
54
- end
55
60
  service = Lifecycle::Service.new(
56
61
  root: store_root(options),
57
- binding: binding_policy,
62
+ binding: Lifecycle::OperatorOnlyBinding.new,
58
63
  policy: policy,
59
64
  socket_path: socket_path(options),
60
65
  group: group,
@@ -67,30 +72,6 @@ module Ace
67
72
 
68
73
  private
69
74
 
70
- # The host/Incus crossing (spec 8xa.t.nh0): authority config,
71
- # credential layout and TLS material are verified fail-closed
72
- # BEFORE the loopback listener opens; the verified project-
73
- # scoped client certificate is the only admitted peer.
74
- def serve_tls_authority(options)
75
- authority = Lifecycle::TlsAuthority.load(options[:tls_authority])
76
- binding_policy = Providers::Lab.assignment_binding(repo_root: options[:repo_root])
77
- service = Lifecycle::TlsService.new(
78
- root: store_root(options),
79
- binding: binding_policy,
80
- project: authority.project_id,
81
- credential: authority.credential,
82
- decision_policy: Providers::Lab.grants_policy(grants_path: grants_path(options)),
83
- ssl_context: authority.ssl_context!,
84
- listen: authority.listen,
85
- port: authority.port,
86
- group: authority.service_group,
87
- logger: options[:verbose] ? ->(message) { warn message } : nil
88
- )
89
- service.run
90
- rescue Lifecycle::Error, Providers::ProviderUnavailableError => e
91
- raise_cli_error(e.message)
92
- end
93
-
94
75
  def dedicated_group(options)
95
76
  group = options[:group].to_s
96
77
  return group unless group.empty?
@@ -45,10 +45,30 @@ module Ace
45
45
  puts "ID: #{event.id}"
46
46
  puts "Title: #{event.title}"
47
47
  puts "Status: #{event.status}"
48
- puts "Kind: #{event.kind}"
48
+ puts "Type: #{event.type}"
49
+ puts "Kind: #{event.kind}" if event.question?
50
+ puts "Result: #{event.result}" if event.report?
49
51
  puts "Tags: #{event.tags.join(", ")}"
50
- puts "Questions: #{event.questions.join(" | ")}"
51
- puts "Answer: #{event.answer || "(none)"}"
52
+ if event.recipient
53
+ puts "Recipient: #{Ace::Hitl::Atoms::HitlAddress.to_s(event.recipient)}"
54
+ end
55
+ if event.reply_to
56
+ puts "Reply-To: #{Ace::Hitl::Atoms::HitlAddress.to_s(event.reply_to)}"
57
+ end
58
+ if event.report?
59
+ puts "Report: #{event.content}"
60
+ else
61
+ puts "Questions: #{event.questions.join(" | ")}"
62
+ puts "Answer: #{event.answer || "(none)"}"
63
+ end
64
+ delivery = event.metadata["delivery"]
65
+ if delivery.is_a?(Hash) && !delivery.empty?
66
+ delivery.sort.each do |route, record|
67
+ detail = record["detail"].to_s.empty? ? "" : " - #{record["detail"]}"
68
+ attempted = record["attempted_at"] ? " (#{record["attempted_at"]})" : ""
69
+ puts "Delivery: #{route} (#{record["adapter"]}): #{record["status"]}#{detail}#{attempted}"
70
+ end
71
+ end
52
72
  puts resolved_location if resolved_location
53
73
  puts "Path: #{event.file_path}"
54
74
  end
@@ -59,6 +59,8 @@ module Ace
59
59
  raise Ace::Support::Cli::Error.new(
60
60
  "HITL event '#{ref}' is ambiguous across scope '#{scope || "all"}'. Candidates: #{candidates}"
61
61
  )
62
+ rescue Ace::Hitl::Organisms::HitlManager::NotAnswerableError => e
63
+ raise Ace::Support::Cli::Error.new(e.message)
62
64
  end
63
65
 
64
66
  raise Ace::Support::Cli::Error.new("HITL event '#{ref}' not found") unless event
@@ -81,6 +83,8 @@ module Ace
81
83
  puts "HITL event archived after successful dispatch."
82
84
  when :managed_request
83
85
  raise Ace::Support::Cli::Error.new("Managed requests require scoped live delivery; unscoped resume is prohibited")
86
+ when :report
87
+ raise Ace::Support::Cli::Error.new("HITL event '#{event.id}' is a report; reports do not await an answer or resume")
84
88
  when :no_answer
85
89
  raise Ace::Support::Cli::Error.new("Cannot resume '#{event.id}' without an answer")
86
90
  when :failed
@@ -47,6 +47,9 @@ module Ace
47
47
 
48
48
  manager = Ace::Hitl::Organisms::HitlManager.new
49
49
  event = manager.show(ref, scope: scope)&.dig(:event)
50
+ if event&.report?
51
+ raise Ace::Support::Cli::Error.new("HITL event '#{event.id}' is a report; reports do not await an answer")
52
+ end
50
53
  if event && event.metadata["lab_request_id"]
51
54
  emit(Ace::Hitl::LiveClient.new(boundary: lifecycle_client).wait(
52
55
  request: event.metadata.fetch("lab_request_id"), timeout: options[:timeout] || 0,
@@ -70,6 +73,8 @@ module Ace
70
73
  puts "Resume: #{resume}" if resume
71
74
  when :timeout
72
75
  raise Ace::Support::Cli::Error.new("Timed out waiting for HITL event '#{ref}'")
76
+ when :report
77
+ raise Ace::Support::Cli::Error.new("HITL event '#{ref}' is a report; reports do not await an answer")
73
78
  else
74
79
  raise Ace::Support::Cli::Error.new("HITL event '#{ref}' not found")
75
80
  end
data/lib/ace/hitl/cli.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require "ace/support/cli"
4
4
  require_relative "../hitl/version"
5
5
  require_relative "cli/commands/create"
6
- require_relative "cli/commands/ask"
6
+ require_relative "cli/commands/send"
7
7
  require_relative "cli/commands/operator_question"
8
8
  require_relative "cli/commands/read"
9
9
  require_relative "cli/commands/show"
@@ -17,10 +17,6 @@ require_relative "cli/commands/pending"
17
17
  require_relative "cli/commands/states"
18
18
  require_relative "cli/commands/duty"
19
19
  require_relative "cli/commands/serve"
20
- require_relative "cli/commands/overseer_send"
21
- require_relative "cli/commands/overseer_pending"
22
- require_relative "cli/commands/overseer_ack"
23
- require_relative "cli/commands/proposal"
24
20
 
25
21
  module Ace
26
22
  module Hitl
@@ -30,43 +26,40 @@ module Ace
30
26
  PROGRAM_NAME = "ace-hitl"
31
27
 
32
28
  REGISTERED_COMMANDS = [
33
- ["proposal", "Create, inspect, revise or resolve an immutable second-commander proposal"],
34
- ["create", "Create HITL event"],
29
+ ["create", "Create HITL event (question or report)"],
35
30
  ["operator-question", "Create a nonsecret question under an installed operator project grant"],
36
31
  ["read", "Read authenticated lifecycle request facts"],
37
- ["ask", "Ask a human via HITL and forward the request to the Lab"],
38
32
  ["show", "Show HITL event details"],
39
33
  ["list", "List HITL events"],
40
34
  ["update", "Update HITL event metadata or answer"],
35
+ ["send", "Send a HITL event through the configured adapter routes"],
41
36
  ["wait", "Wait for an answer on a specific HITL event"],
42
37
  ["deliver", "Deliver an answer (stdin) to a pending HITL relay request"],
43
38
  ["consume", "Wait for and consume the answer of one own HITL relay request"],
44
39
  ["cancel", "Cancel one HITL relay request with an audited reason"],
45
40
  ["pending", "List answerable HITL relay requests (transport)"],
46
41
  ["states", "List public HITL lifecycle projections (transport)"],
47
- ["duty", "Project pending and escalated HITL requests (transport)"],
48
- ["overseer-send", "Queue a bounded, type-tagged Overseer response (stdin)"],
49
- ["overseer-pending", "List queued Overseer responses (host-broker)"],
50
- ["overseer-ack", "Acknowledge one relayed Overseer response"]
42
+ ["duty", "Project pending and escalated HITL requests (transport)"]
51
43
  ].freeze
52
44
 
53
45
  HELP_EXAMPLES = [
54
46
  "ace-hitl list --status pending",
55
47
  "ace-hitl show abc123 --content",
56
48
  "ace-hitl create \"Which auth strategy?\" --kind decision",
57
- "ace-hitl ask --question \"Proceed with deploy?\" --assignment assign685 --attempt attempt685 --effect-arg /bin/false --effect-cwd /tmp",
49
+ "ace-hitl create \"Blocked on auth\" --kind decision --to overseer --task 8xg.t.sbz.1.1",
50
+ "ace-hitl create \"Work done\" --type report --result success --task 8xg.t.sbz.1.0",
58
51
  "ace-hitl update abc123 --answer \"Use JWT with refresh tokens\"",
52
+ "ace-hitl send 8xhkep",
59
53
  "ace-hitl wait abc123 --poll-every 600 --timeout 14400"
60
54
  ].freeze
61
55
 
62
56
  register "create", CLI::Commands::Create
63
- register "proposal", CLI::Commands::Proposal
64
- register "ask", CLI::Commands::Ask
65
57
  register "operator-question", CLI::Commands::OperatorQuestion
66
58
  register "read", CLI::Commands::Read
67
59
  register "show", CLI::Commands::Show
68
60
  register "list", CLI::Commands::List
69
61
  register "update", CLI::Commands::Update
62
+ register "send", CLI::Commands::Send
70
63
  register "wait", CLI::Commands::Wait
71
64
  register "deliver", CLI::Commands::Deliver
72
65
  register "consume", CLI::Commands::Consume
@@ -75,9 +68,6 @@ module Ace
75
68
  register "states", CLI::Commands::States
76
69
  register "duty", CLI::Commands::Duty
77
70
  register "serve", CLI::Commands::Serve
78
- register "overseer-send", CLI::Commands::OverseerSend
79
- register "overseer-pending", CLI::Commands::OverseerPending
80
- register "overseer-ack", CLI::Commands::OverseerAck
81
71
 
82
72
  version_cmd = Ace::Support::Cli::VersionCommand.build(
83
73
  gem_name: "ace-hitl",
@@ -43,21 +43,6 @@ module Ace
43
43
  def execute(store, op, params)
44
44
  case op
45
45
  when "ping" then store.capabilities
46
- when "proposal-create" then store.proposal_create(**symbolize(params))
47
- when "proposal-show" then store.proposal_show(required(params, "id"), project: required(params, "project"), history_after: params.fetch("history_after", 0))
48
- when "proposal-revise" then store.proposal_revise(required(params, "id"), project: required(params, "project"), expected_revision: required(params, "expected_revision"),
49
- operation_id: required(params, "operation_id"), document: required(params, "document"))
50
- when "proposal-ack"
51
- store.proposal_acknowledge(required(params, "id"), project: required(params, "project"), submitted_at: required(params, "submitted_at"))
52
- when "proposal-reply"
53
- store.proposal_reply(required(params, "id"), project: required(params, "project"), answer: required(params, "answer"),
54
- received_at: required(params, "received_at"), sequence: required(params, "sequence"))
55
- when "proposal-reconcile"
56
- store.proposal_reconcile(required(params, "id"), project: required(params, "project"), checkpoint: required(params, "checkpoint"))
57
- when "proposal-wake" then store.proposal_wake(project: required(params, "project"), after: params["after"])
58
- when "proposal-due" then store.proposal_due(project: required(params, "project"), after: params["after"])
59
- when "proposal-history" then store.proposal_history(project: required(params, "project"),
60
- query: params.fetch("query", ""), after: params["after"])
61
46
  when "operator-question" then store.operator_question(**symbolize(params))
62
47
  when "create" then store.create(**symbolize(params))
63
48
  when "read" then store.read(required(params, "id"))
@@ -11,7 +11,7 @@ module Ace
11
11
  module Kinds
12
12
  ALL = %w[
13
13
  text choice confirm secret review
14
- question decision verification otp proposal
14
+ question decision verification otp
15
15
  ].freeze
16
16
  SECRET = %w[otp].freeze
17
17
 
@@ -13,42 +13,6 @@ module Ace
13
13
  request("operator-question", params.transform_keys(&:to_s))
14
14
  end
15
15
 
16
- def proposal_create(**params)
17
- request("proposal-create", params)
18
- end
19
-
20
- def proposal_show(id, project:, history_after: 0)
21
- request("proposal-show", {"id" => id, "project" => project, "history_after" => history_after})
22
- end
23
-
24
- def proposal_revise(id, project:, expected_revision:, operation_id:, document:)
25
- request("proposal-revise", {"id" => id, "project" => project, "expected_revision" => expected_revision, "operation_id" => operation_id, "document" => document})
26
- end
27
-
28
- def proposal_acknowledge(id, project:, submitted_at:)
29
- request("proposal-ack", {"id" => id, "project" => project, "submitted_at" => submitted_at})
30
- end
31
-
32
- def proposal_reply(id, project:, **params)
33
- request("proposal-reply", {"id" => id, "project" => project}.merge(params))
34
- end
35
-
36
- def proposal_reconcile(id, project:, checkpoint:)
37
- request("proposal-reconcile", {"id" => id, "project" => project, "checkpoint" => checkpoint})
38
- end
39
-
40
- def proposal_wake(project:, after: nil)
41
- request("proposal-wake", {"project" => project, "after" => after}.compact)
42
- end
43
-
44
- def proposal_due(project:, after: nil)
45
- request("proposal-due", {"project" => project, "after" => after}.compact)
46
- end
47
-
48
- def proposal_history(project:, query: "", after: nil)
49
- request("proposal-history", {"project" => project, "query" => query, "after" => after}.compact)
50
- end
51
-
52
16
  def create(deadline: nil, **params)
53
17
  request("create", params.transform_keys(&:to_s), deadline: deadline)
54
18
  end
@@ -106,17 +106,6 @@ module Ace
106
106
  false
107
107
  end
108
108
 
109
- def proposal?(_peer, project:)
110
- false
111
- end
112
-
113
- def proposal_silence?(caller_uid:, requester:, project:, operation:)
114
- false
115
- end
116
-
117
- def silence_operation?(project:, operation:)
118
- false
119
- end
120
109
  # @return [Boolean] true when the peer may deliver answers and
121
110
  # observe pending/states for the given project
122
111
  def transport?(_peer, project: nil)
@@ -170,41 +159,8 @@ module Ace
170
159
  uid.is_a?(Integer) ? uid : nil
171
160
  end
172
161
 
173
- # Decision admission is explicitly installed, never inferred from
174
- # an ordinary requester or transport grant.
175
- def proposal?(peer, project:)
176
- current = document
177
- proposal_principal?(peer.uid, project, current)
178
- end
179
-
180
- # A proposer grant allows asking. Only this separate, exact project
181
- # operation list delegates a decision after the acknowledged window.
182
- def proposal_silence?(caller_uid:, requester:, project:, operation:)
183
- current = document
184
- proposal_principal?(caller_uid, project, current) &&
185
- delegated_operation?(project, operation, current)
186
- end
187
-
188
- def silence_operation?(project:, operation:)
189
- delegated_operation?(project, operation, document)
190
- end
191
-
192
162
  private
193
163
 
194
- def proposal_principal?(uid, project, current)
195
- uids = hitl_facts(current)["proposal_uids"]
196
- projects = authorized_projects(uid, current)
197
- uid.is_a?(Integer) && uids.is_a?(Array) && uids.include?(uid) && projects &&
198
- (projects.empty? || projects.include?(project.to_s))
199
- end
200
-
201
- def delegated_operation?(project, operation, current)
202
- delegations = hitl_facts(current)["silence_operations"]
203
- operations = delegations.is_a?(Hash) && delegations[project]
204
- operations.is_a?(Array) && operation.is_a?(String) &&
205
- operation.match?(/\A[a-z][a-z0-9-]{0,63}\z/) && operations.include?(operation)
206
- end
207
-
208
164
  def document
209
165
  @document || TrustedFile.read_yaml(@grants_path) || {}
210
166
  end
@@ -14,8 +14,7 @@ module Ace
14
14
  VERSION = 1
15
15
  MAX_FRAME_BYTES = 64 * 1024
16
16
  DEFAULT_DEADLINE_SECONDS = 10.0
17
- OPERATIONS = %w[operator-question create read deliver consume cancel pending states ping
18
- proposal-create proposal-show proposal-revise proposal-ack proposal-reply proposal-reconcile proposal-due proposal-history proposal-wake].freeze
17
+ OPERATIONS = %w[operator-question create read deliver consume cancel pending states ping].freeze
19
18
 
20
19
  # Wire failures map to the transport error; classified lifecycle
21
20
  # failures pass through under their own names.
@@ -41,7 +41,7 @@ module Ace
41
41
 
42
42
  def initialize(root:, binding:, policy:, socket_path:, group: Store::DEFAULT_GROUP,
43
43
  deadline_seconds: Protocol::DEFAULT_DEADLINE_SECONDS, vault: nil, logger: nil,
44
- idle_seconds: DEFAULT_IDLE_SECONDS, proposal_clock: -> { Time.now.utc })
44
+ idle_seconds: DEFAULT_IDLE_SECONDS)
45
45
  @root = root
46
46
  @binding = binding
47
47
  @policy = policy
@@ -51,7 +51,6 @@ module Ace
51
51
  @vault = vault || OtpVault::MemoryVault.new
52
52
  @logger = logger
53
53
  @idle_seconds = idle_seconds
54
- @proposal_clock = proposal_clock
55
54
  @max_connections = MAX_CONNECTIONS
56
55
  @connections = 0
57
56
  @handler_threads = []
@@ -249,7 +248,7 @@ module Ace
249
248
  policy: @policy,
250
249
  identity: peer,
251
250
  vault: @vault,
252
- proposal_clock: @proposal_clock, stopping: -> { @stopping }, group: @group
251
+ stopping: -> { @stopping }, group: @group
253
252
  )
254
253
  end
255
254