ctx_driver 0.4.0 → 0.5.1

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: 4568b50606564c6f379c1110bd52c00566cfc7868f59f4253e38f675e2cf11db
4
- data.tar.gz: 32e3dc04f8ee25ec7c21dd6c52beb607f0a51ecf2e80b4d39a30b125a28f70d4
3
+ metadata.gz: 3ee87be253781477d6195addd5fa41f569ba288803669edf7c51013fe2837990
4
+ data.tar.gz: c921a73b694f4116c4abc2fd3c5a6a2cab63608a4f7e47855f0b7d78d58daac0
5
5
  SHA512:
6
- metadata.gz: af9f05c3a2fa5f0f9324641a2d81dc95a0488ab0ff7fccc50ec1302852f04e75b074015c34390f27ce7c66aa9a9bfb8ced2916f29c2878656929d85c6ea35cf8
7
- data.tar.gz: 8527273f5407104dfa36c6414d0b65a5a2ecc9991a1572c408044b435fa8792346f4c923fabda6ee2dd365df6a1bc793a2cce3fa325d98e155282df8e63d66e2
6
+ metadata.gz: 654a251a4ed0f768d66f89b2f9b6d22c143ace793e11ffd54a4f449bef38065d9f558d4b2b2cca11f152fcd7916d6bdbd8cebe12843d5ae3fbb4880f1d68d400
7
+ data.tar.gz: 8de685f056456da35796d7d4631b233db79fb115745f6c72768d0a73c8429f61688cecbe6a2650089a2c71784f658382f685f69176c2225951b3384e235d4bd2
data/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ Changes to the public contract are recorded here. Versions follow Semantic Versi
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.5.1 (2026-10-30)
8
+
9
+ - Accept ctx_parser 0.4 and 0.5 while preserving command, receipt and source contracts.
10
+
11
+ ## 0.5.0 (2026-10-30)
12
+
13
+ - Introduce immutable `Source` values with validated digest/byte pairs, timestamps and domain context. Retain acquired digests on clock failure. Share single-command receipt validation between reads and writes; borrowed configuration writes never reconnect.
14
+
7
15
  ## 0.4.0 (2026-10-29)
8
16
 
9
17
  - Use the parser command contract for borrowed-session reads; preserve single-command identity and stop-after-desynchronization behavior.
data/README.md CHANGED
@@ -122,4 +122,8 @@ MIT. See [LICENSE](LICENSE).
122
122
 
123
123
  Receipt protocol: each completed step is `{ command: String }` with exactly the requested command bytes. Success requires one step; failure permits zero or one. Opaque placeholder steps are no longer accepted. Missing or mismatched identity yields `invalid_command_result` and stops later queries while retaining raw fingerprints. Session adapts native net-connector steps automatically.
124
124
 
125
- 本次使用 ctx_parser 0.4 系列契约;`Result` 新增 `covered_scopes`,单行命令统一由 `CtxParser::Commands.command_text` 校验。跨模块约定见 [ctx_runner 契约迁移](https://github.com/gatework/ctx_runner/blob/main/docs/contracts-2026-10-29.md)。
125
+ ctx_driver 0.5.1 supports ctx_parser 0.4 and 0.5. Commands still use `CtxParser::Commands.command_text`; receipt and source fields are unchanged. See the [parser 0.5 migration](https://github.com/gatework/ctx_parser/blob/main/docs/contracts-0.5.md) for profile declaration and snapshot ownership changes.
126
+
127
+ ## Domain contract migration (2026-10-30)
128
+
129
+ Introduce immutable `Source` values with validated digest/byte pairs, timestamps and domain context. Retain acquired digests on clock failure. Share single-command receipt validation between reads and writes; borrowed configuration writes never reconnect. See the [schema 4 migration and module contracts](https://github.com/gatework/ctx_runner/blob/main/docs/contracts-2026-10-30.md). Regenerate old collection reports and change plans; changing only their version number is not supported.
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "ctx_parser/immutable"
4
4
  require_relative "command"
5
+ require_relative "source"
5
6
 
6
7
  module CtxDriver
7
8
  # 原始正文须显式读取;默认检查与序列化仅展示采集元数据。
@@ -15,7 +16,7 @@ module CtxDriver
15
16
  unless started_at.is_a?(Time) && completed_at.is_a?(Time)
16
17
  raise ArgumentError, "Capture timestamps must be Time values"
17
18
  end
18
- unless evidence.is_a?(Array) && evidence.all? { |entry| entry.is_a?(Hash) }
19
+ unless evidence.is_a?(Array) && evidence.all? { |entry| (entry.is_a?(Hash) || entry.is_a?(Source)) }
19
20
  raise ArgumentError, "Capture evidence must be hashes"
20
21
  end
21
22
  raise ArgumentError, "Capture failure must be a hash or nil" unless failure.nil? || failure.is_a?(Hash)
@@ -28,7 +29,8 @@ module CtxDriver
28
29
  @body = body.nil? || body.frozen? ? body : body.dup.freeze
29
30
  @started_at = started_at.frozen? ? started_at : started_at.dup.freeze
30
31
  @completed_at = completed_at.frozen? ? completed_at : completed_at.dup.freeze
31
- @evidence, @failure = [ evidence, failure ].map { |value| CtxParser::Immutable.copy(value) }
32
+ @evidence = evidence.map { |entry| Source.coerce(entry) }.freeze
33
+ @failure = CtxParser::Immutable.copy(failure)
32
34
  @skipped = skipped
33
35
  freeze
34
36
  end
@@ -41,7 +43,7 @@ module CtxDriver
41
43
  # 返回公开的数据结构,并保留状态与诊断信息。
42
44
  def to_h
43
45
  { command: command, started_at: started_at, completed_at: completed_at,
44
- evidence: evidence, failure: failure, skipped: skipped? }
46
+ evidence: evidence.map(&:to_h), failure: failure, skipped: skipped? }
45
47
  end
46
48
 
47
49
  # 序列化诊断元数据,不包含原始来源文本。
@@ -5,6 +5,7 @@ require "time"
5
5
  require_relative "capture"
6
6
  require_relative "failure"
7
7
  require_relative "session"
8
+ require_relative "receipt"
8
9
 
9
10
  module CtxDriver
10
11
  # 借用已打开会话逐条独立查询;连接归属与领域判断由外部负责。
@@ -66,14 +67,7 @@ module CtxDriver
66
67
  raise TypeError, "Command output must be text" unless output.is_a?(String)
67
68
 
68
69
  evidence = [ { command: command, sha256: Digest::SHA256.hexdigest(output), bytes: output.bytesize } ]
69
- success = receipt.success?
70
- steps = receipt.steps
71
- unless [ true, false ].include?(success) && steps.is_a?(Array) && (success ? steps.one? : steps.size <= 1)
72
- raise TypeError, "One query must return one completed command"
73
- end
74
- unless steps.all? { |step| step.is_a?(Hash) && step[:command].is_a?(String) && step[:command].b == command.b }
75
- raise TypeError, "Completed step must belong to the requested command"
76
- end
70
+ success = Receipt.validate(receipt, command: command)
77
71
  receipt_validated = true
78
72
  if success
79
73
  body = receipt.value!
@@ -96,7 +90,6 @@ module CtxDriver
96
90
  evidence = [ evidence.first.merge(observed_at: observed_at) ]
97
91
  end
98
92
  rescue StandardError => error
99
- evidence = []
100
93
  command_failure ||= Failure.details(error, fallback: :invalid_command_result, stage: :response)
101
94
  end
102
95
  state_failure = detect_session_failure
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CtxDriver
4
+ # 读取与配置写入共用单命令回执身份,成功恰好对应一个完成步骤。
5
+ module Receipt
6
+ def self.validate(receipt, command:)
7
+ success, steps = receipt.success?, receipt.steps
8
+ unless [ true, false ].include?(success) && steps.is_a?(Array) && (success ? steps.one? : steps.size <= 1)
9
+ raise TypeError, "One query must return one completed command"
10
+ end
11
+ unless steps.all? { |step| step.is_a?(Hash) && step[:command].is_a?(String) && step[:command].b == command.b }
12
+ raise TypeError, "Completed step must belong to the requested command"
13
+ end
14
+ success
15
+ end
16
+ end
17
+ end
@@ -47,6 +47,13 @@ module CtxDriver
47
47
  # 复用连接器的真实状态,避免失败后隐式重连。
48
48
  def connected? = @session.connected?
49
49
 
50
+ # 配置写入使用连接器的模式提示符;借用已连接会话,不能触发隐式重连。
51
+ def write_command(command)
52
+ raise IOError, "Session is closed" unless connected?
53
+ command = Command.snapshot(command)
54
+ Response.new(@session.execute_command(command, output_sensitive: true))
55
+ end
56
+
50
57
  # 单命令操作保护响应正文,并绑定当前完整提示符。
51
58
  def send_commands(commands)
52
59
  raise ArgumentError, "Expected one query" unless commands.is_a?(Array) && commands.one?
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ctx_parser/immutable"
4
+ require "ctx_parser/values"
5
+ require_relative "command"
6
+
7
+ module CtxDriver
8
+ # 来源身份与摘要是值;领域附加信息放入 context,不混入驱动字段。
9
+ class Source < Data.define(:command, :sha256, :bytes, :reference, :observed_at, :status, :context)
10
+ def self.coerce(value)
11
+ return value if value.is_a?(self)
12
+ raise ArgumentError, "Source must be a mapping" unless value.is_a?(Hash)
13
+ attributes = value.each_with_object({}) do |(key, entry), result|
14
+ unless (key.is_a?(String) || key.is_a?(Symbol)) && members.include?(key.to_sym) && !result.key?(key.to_sym)
15
+ raise ArgumentError, "Source contains unknown or duplicate fields"
16
+ end
17
+ result[key.to_sym] = entry
18
+ end
19
+ raise ArgumentError, "Invalid source status" if attributes.key?(:status) && attributes[:status].nil?
20
+ attributes[:status] = attributes[:status].to_sym if value.key?("status") && attributes[:status].is_a?(String)
21
+ new(**attributes)
22
+ end
23
+
24
+ def initialize(command: nil, sha256: nil, bytes: nil, reference: nil, observed_at: nil, status: nil, context: {})
25
+ command = Command.snapshot(command) unless command.nil?
26
+ unless sha256.nil? && bytes.nil? || sha256.is_a?(String) && /\A[0-9a-f]{64}\z/.match?(sha256) && bytes.is_a?(Integer) && bytes >= 0
27
+ raise ArgumentError, "Source requires a digest and nonnegative byte count together"
28
+ end
29
+ unless reference.nil? || reference.is_a?(String) && !reference.strip.empty? && !/[\x00-\x1f\x7f]/.match?(reference)
30
+ raise ArgumentError, "Invalid source reference"
31
+ end
32
+ raise ArgumentError, "Source requires an identity or digest" unless command || reference || sha256
33
+ unless status.nil? || %i[complete partial failed unsupported].include?(status)
34
+ raise ArgumentError, "Invalid source status"
35
+ end
36
+ raise ArgumentError, "Source context must be a mapping" unless context.is_a?(Hash)
37
+ observed_at = CtxParser::Values.timestamp(observed_at) if observed_at.is_a?(String)
38
+ raise ArgumentError, "Source time must be a Time or nil" unless observed_at.nil? || observed_at.is_a?(Time)
39
+ attributes = { command: command, sha256: sha256&.dup&.freeze, bytes: bytes, reference: reference&.dup&.freeze,
40
+ observed_at: observed_at&.getutc&.freeze, status: status, context: CtxParser::Immutable.copy(context) }
41
+ metadata = attributes.compact
42
+ metadata.delete(:context) if context.empty?
43
+ metadata[:observed_at] = attributes[:observed_at].iso8601(6).freeze if observed_at
44
+ @metadata = metadata.freeze
45
+ super(**attributes)
46
+ end
47
+
48
+ def verified? = !sha256.nil? && !bytes.nil?
49
+ def to_h = @metadata.dup
50
+
51
+ # 元数据映射访问沿用同一字段协议;所有更新重新经过构造校验。
52
+ def [](key) = @metadata[key]
53
+ def fetch(...) = @metadata.fetch(...)
54
+ def key?(key) = @metadata.key?(key)
55
+ def keys = @metadata.keys
56
+ def merge(**attributes) = with(**attributes)
57
+ def inspect = "#<#{self.class.name} verified=#{verified?}>"
58
+ alias to_s inspect
59
+ def pretty_print(printer) = printer.text(inspect)
60
+ end
61
+ end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module CtxDriver
4
4
  # 公开包版本,供 RubyGems 元数据和运行时调用方共用。
5
- VERSION = "0.4.0"
5
+ VERSION = "0.5.1"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ctx_driver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gatework contributors
@@ -18,7 +18,7 @@ dependencies:
18
18
  version: 0.4.0
19
19
  - - "<"
20
20
  - !ruby/object:Gem::Version
21
- version: '0.5'
21
+ version: '0.6'
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -28,7 +28,7 @@ dependencies:
28
28
  version: 0.4.0
29
29
  - - "<"
30
30
  - !ruby/object:Gem::Version
31
- version: '0.5'
31
+ version: '0.6'
32
32
  - !ruby/object:Gem::Dependency
33
33
  name: digest
34
34
  requirement: !ruby/object:Gem::Requirement
@@ -76,7 +76,9 @@ files:
76
76
  - lib/ctx_driver/command.rb
77
77
  - lib/ctx_driver/failure.rb
78
78
  - lib/ctx_driver/reader.rb
79
+ - lib/ctx_driver/receipt.rb
79
80
  - lib/ctx_driver/session.rb
81
+ - lib/ctx_driver/source.rb
80
82
  - lib/ctx_driver/version.rb
81
83
  homepage: https://github.com/gatework/ctx_driver
82
84
  licenses: