5pm 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7d607b0dac482d750b2ad2477f3265aa944ce21e1742c6c564dd698cf83c2e11
4
+ data.tar.gz: f9f54064b2b684aa0255dad732b6a84017e829847df9caa65d14acb0cc910a6a
5
+ SHA512:
6
+ metadata.gz: 15dc1d3ef35febd811b62a9539b3da45cc99175648b780c75b3fb7e6c24b207260cad0fe85bd3498ae9ac553dfafba168ee862a37053f62db93e02f6925c3075
7
+ data.tar.gz: 28c70a2fe3f5f8326960cf74e8f126c0783cf091dfb40c8852eb7451eb73e4b6d6bc8faa88ce76e9305c115a3022cdc53fbe2cb527578f023700235884fe1197
data/CHANGELOG.md ADDED
@@ -0,0 +1,66 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 — 2026-10-30
4
+
5
+ - Make live API validation optional and owner-controlled. Releases no longer require
6
+ an active 5PM account or fresh live evidence; cached reports never expire or force
7
+ a refresh after source changes. Offline tests and artifact checks remain required.
8
+
9
+ - Restrict releases to manual dispatch from master for an explicitly approved
10
+ version and exact commit; retain protected environment approval for tag/publish jobs.
11
+
12
+ - Align packaging/release bin scripts with abbu and printavo: shared Bash runner,
13
+ Ruby support helpers, pkg artifacts, isolated installs and read-only tag verification.
14
+
15
+ - Name the packages `5pm` and `5pm-mcp`, with matching dependencies, builds and
16
+ release configuration; retain the `FivePM` Ruby namespace and require paths.
17
+
18
+ - Prepare protected GitHub OIDC publishing for both gems, with exact-commit release
19
+ checks, separate tag permissions, and standard Bundler build/release tasks.
20
+
21
+ - Retain live cleanup ledger entries until deletion is confirmed; reject false/null/zero
22
+ results and independently verify absence when lookup errors use a generic API code.
23
+
24
+ - Recognize mapped response parts in presence checks, verify attachment absence after
25
+ deletion, and bound live subprocess exit and pipe draining with process-group cleanup.
26
+
27
+ - Validate UTF-8 JSON after streamed chunks are assembled; preserve arbitrary bytes
28
+ when probing binary downloads for API error envelopes.
29
+
30
+ - Ship RBS for both gems, including all generated model readers and MCP callback
31
+ contracts; validate source and installed-package signatures in the default checks.
32
+
33
+ - Live disposable-account contract, CRUD, pagination, MCP stdio and mutation-approval verification.
34
+ - Redacted numeric `APIError` for HTTP-200 errors, including code 0 validation errors.
35
+ - Preserve live integer deletion markers and add bounded binary `Download` results.
36
+ - Opt-in reproducible live suite with separate mutation enablement and cleanup checks.
37
+
38
+ - Accept live JSON booleans as well as WSDL strings for `User.canViewProfiles`,
39
+ preserving both representations without relaxing other string fields.
40
+
41
+ - Accept the observed sign-in JSON envelope `data.sessionId`, retain the original
42
+ WSDL-shaped envelope, and reject ambiguous authentication responses.
43
+ - Map operation-specific `data` envelopes in typed readers, pagination and MCP,
44
+ preserving raw wire JSON and false scalar results without replaying mutations.
45
+
46
+ - Initial SDK scaffold, contract inventory, POST transport and synchronized sessions.
47
+ - Generic service calls validated against public demo WSDL POST bindings.
48
+ - CRUD/list helpers for all five resource services, synthetic RSpec contracts, CI and packaging.
49
+ - Opt-in immutable typed responses for all schema response types, with lossless raw JSON,
50
+ snake_case readers, nested typed lists and strict scalar normalization.
51
+ - Synthetic JSON fixtures and schema parity tests; live envelope verification remains pending.
52
+
53
+ - Bounded opt-in read retries and pagination; streamed response-size limits and specific HTTP errors.
54
+ - Pre-I/O request validation, rejected-session invalidation and copied credentials.
55
+ - Explicit user/authentication/attachment helpers and typed-model request round trips.
56
+ - Fix Enumerable overriding typed list `to_h`.
57
+ - Isolated packaging, read-only release gates and opt-in sandbox preflight.
58
+
59
+ - Separate 5pm-mcp package with official MCP SDK protocol handling, default read-only
60
+ tools, per-request operator approval for embedded mutations and redacted audit events.
61
+ - Real synthetic stdio/executable tests and isolated installation checks for both artifacts.
62
+ - Host-configurable MCP tool allowlists and stdio confirmation-enforcement regression tests.
63
+
64
+ - Review fixes: retain typed-list serialization in the base layer, include DNS lookup
65
+ in timeout handling, reject stale live-validation evidence after code changes, and
66
+ emit successful MCP audit outcomes only after result serialization succeeds.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stan Carver II
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,209 @@
1
+ # 5pm
2
+
3
+ Ruby SDK for 5pm API v2. MIT licensed; Ruby 3.2+.
4
+
5
+ Unreleased. Disposable-account verification covers account-specific contracts, JSON
6
+ envelopes, resource CRUD, pagination, attachments and the standalone MCP adapter.
7
+ See [live validation](docs/live-validation.md) for scope and reproducible checks.
8
+ Publication still requires review and explicit approval.
9
+
10
+ ## Development
11
+
12
+ ```sh
13
+ bundle install
14
+ bin/spec
15
+ bin/lint
16
+ bin/package-mcp
17
+ ```
18
+
19
+ For repeatable macOS Keychain setup and cached contract/read checks, see
20
+ [cached validation](docs/cached-validation.md) (`bin/validate --help`).
21
+
22
+ ## Configuration and initial API
23
+
24
+ Install from this checkout with Bundler (`gem "5pm", path: "/path/to/sdk"`).
25
+ The gem name is `5pm`; load it with `require "five_pm"` and use the `FivePM` namespace.
26
+ Provide `FIVE_PM_HOST`, `FIVE_PM_LOGIN`, and `FIVE_PM_PASSWORD` through your environment
27
+ or a secret manager such as Infisical; no dotenv file is needed.
28
+
29
+ ```ruby
30
+ require "five_pm"
31
+ client = FivePM::Client.new(
32
+ host: ENV.fetch("FIVE_PM_HOST"), # account.5pmweb.com, without scheme or path
33
+ login: ENV.fetch("FIVE_PM_LOGIN"),
34
+ password: ENV.fetch("FIVE_PM_PASSWORD"),
35
+ open_timeout: 5, read_timeout: 30, write_timeout: 30
36
+ )
37
+ response = client.tasks.list(filter: { projectId: 123 }, offset: 0, count: 20)
38
+ # Explicit mutations, for a disposable account only:
39
+ # client.tasks.create(name: "Example task", projectId: 123)
40
+ # client.tasks.update(id: 456, name: "Updated task")
41
+ # client.tasks.get(456)
42
+ # client.tasks.delete(456)
43
+ # Exact wire operations are also available:
44
+ # client.users.call("getByEmail", email: "person@example.com")
45
+ client.sign_out
46
+ ```
47
+
48
+ Services: `authentication`, `projects`, `project_groups`, `tasks`, `activity`, `users`.
49
+ CRUD resources expose `create`, `update`, `get`, `delete`, and `list`. For users and
50
+ project groups, `list` uses `getAll` and rejects unsupported filtering/pagination options.
51
+ Filters and other wire options retain their documented camelCase names.
52
+
53
+ Responses retain their full JSON structure and unknown fields. The initial sign-in parser
54
+ accepts `data.sessionId`, observed in the disposable test account, and the original
55
+ WSDL-shaped `auth.sessionId` envelope. Ambiguous envelopes are rejected; resource
56
+ response mapping is exercised by the live suite. Pagination and read retries are explicit opt-ins;
57
+ there is no date/time conversion. Caller-supplied
58
+ parameters retain wire names and values. Authentication refresh is synchronized with a
59
+ 30-minute TTL and 30-second margin. Authentication HTTP failures invalidate the rejected session for the next explicit call without replay.
60
+ All calls use form-encoded HTTPS POST; redirects and environment proxies are disabled.
61
+ Response bodies stream into a bounded buffer (5 MiB by default, configurable with `max_response_bytes`).
62
+
63
+ See [API inventory](docs/api-support.md), [security policy](SECURITY.md),
64
+ [contributing](CONTRIBUTING.md), and [MCP follow-up plan](docs/mcp-plan.md).
65
+
66
+ ## Typed responses (opt-in)
67
+
68
+ Both gems ship RBS signatures in `sig/`. Core signatures cover the client, resources,
69
+ models, pagination, downloads and errors; adapter signatures cover configuration,
70
+ approval proposals and audit events. Raw/typed configuration is selected at runtime,
71
+ so client/resource result types are unions; narrow a result to `FivePM::Response`
72
+ before using model readers. Unknown JSON fields remain recursive JSON values.
73
+ The MCP SDK boundary is a structural interface because that dependency has no RBS.
74
+ `bundle exec rake signatures` validates both sets, and package checks validate the
75
+ signatures extracted from the built artifacts. RBS is a development-only dependency.
76
+
77
+ Pass `response_mode: :typed` to `FivePM::Client.new` to receive a `FivePM::Response`.
78
+ The default `:raw` mode keeps the original Hash/Array behavior. Both resource helpers
79
+ and generic calls honor the selected mode; `sign_in` still returns the client.
80
+
81
+ ```ruby
82
+ client = FivePM::Client.new(
83
+ host: ENV.fetch("FIVE_PM_HOST"), login: ENV.fetch("FIVE_PM_LOGIN"),
84
+ password: ENV.fetch("FIVE_PM_PASSWORD"), response_mode: :typed
85
+ )
86
+ response = client.tasks.list(filter: { projectId: 123 }, count: 20)
87
+ response.list.each do |task| # FivePM::Models::TaskList is Enumerable
88
+ puts [task.id, task.name, task.hours_done].inspect
89
+ end
90
+ response.list.count # server-provided page count, when present
91
+ response.list.total # server-provided total, when present
92
+ response.to_h # original JSON envelope with original wire keys and values
93
+ response.to_json # serializes the original JSON, including unknown fields
94
+ ```
95
+
96
+ Models cover every complex response type in the public demo schema: projects, tasks,
97
+ users, project groups, activity, files, image metadata, authentication tokens, and lists.
98
+ Readers use snake_case (`project_id`, `first_name`); `[]` uses original wire keys
99
+ (`task["projectId"]`). Unknown fields remain available through `[]` and `to_h`.
100
+ Nested lists map their `items` to the appropriate model. `count` and `total` are
101
+ metadata, not automatic pagination or a guarantee of complete results.
102
+
103
+ Schema integers and finite floats accept JSON numbers or decimal strings. Schema
104
+ booleans accept JSON booleans, integer 0/1, and strings `false`/`true`/`0`/`1`.
105
+ String fields remain strings even if they look numeric or boolean. All date/time
106
+ fields remain exact strings; no zone is inferred and no date parsing is attempted.
107
+ Exception: `User.canViewProfiles` accepts both WSDL-declared strings and JSON booleans
108
+ observed in the test account; its reader preserves either representation unchanged.
109
+ Missing readers return nil; `key?` distinguishes missing fields from explicit null.
110
+ For responses with a `data` wrapper, `key?` recognizes mapped reader parts as well
111
+ as original keys; `raw.key?` checks only the wire envelope.
112
+ Malformed known types raise `FivePM::ProtocolError` without including field values;
113
+ these errors never trigger a replay of the request. Unknown fields are not validated.
114
+
115
+ Models and their JSON snapshots are immutable and do not freeze or mutate the caller's
116
+ input. `to_h`/`raw` return a frozen snapshot with original scalar values; typed readers
117
+ may return normalized values. Inspection omits all payloads, including unknown fields.
118
+ Serialization deliberately includes the complete payload, which can contain secrets.
119
+
120
+ Typed readers support both WSDL response part names and JSON `data` wrappers.
121
+ For example, `{"data":{"id":"7"}}` for getAuthorizedUser maps to `response.user`,
122
+ and a list operation's `data` maps to `response.list`. Scalar results, including false,
123
+ map to the operation's declared part. `to_h`/`raw`/`to_json` retain the original wire
124
+ envelope; raw client mode also stays unchanged. Ambiguous wrappers and error-marked
125
+ data wrappers raise `ProtocolError`. Users' optional top-level `sessionId` remains
126
+ separate from the list data. Pagination and MCP use the same envelope mapping.
127
+
128
+ The `data` wrapper is covered by synthetic regressions and live operation checks. List models continue to
129
+ require the XSD `items`/`count`/`total` structure; no singleton or flat-array conversion
130
+ is inferred.
131
+
132
+ ## Bounded pagination, retries and attachments
133
+
134
+ `tasks`, `projects` and `activity` expose lazy `each_page` and `each_item` enumerators:
135
+
136
+ ```ruby
137
+ client.tasks.each_item(filter: { projectId: 123 }, count: 100, max_pages: 20).take(150)
138
+ ```
139
+
140
+ Enumerators request pages only as consumed, advance offset by received item count,
141
+ and stop on empty or short pages. They reject oversized/repeated pages, incompatible
142
+ `list.items` shapes and exhausted `max_pages` bounds rather than silently truncating.
143
+ They do not rely on totals, assume a stable dataset, or guarantee a consistent snapshot
144
+ while account data changes. Bounded offset pagination is exercised in live checks.
145
+
146
+ `read_retries: 0` (default) disables application retries. Set 1–3 to enable retries only
147
+ for the explicit read allowlist in `FivePM::RetryPolicy::READS`. Authentication sign-in,
148
+ all mutations and unknown operations are excluded, even after timeouts. Transient HTTP
149
+ 429/500/502/503/504 and network errors use bounded jittered exponential backoff.
150
+ `max_retry_delay: 30` bounds each pause. Numeric Retry-After is honored; longer than
151
+ allowed delays, malformed values and HTTP-date headers are surfaced without retry.
152
+ There is no automatic replay after authentication or JSON/protocol failures.
153
+
154
+ HTTP errors expose numeric `status`; `RateLimitError` also exposes `retry_after` and
155
+ `retryable?`. `NotFoundError` and `ServerError` specialize `HTTPError`. Remote error
156
+ bodies are never included in exceptions. HTTP-200 numeric `code`/`message` error
157
+ envelopes raise `APIError` with numeric `code`, including code 0 validation failures.
158
+ Remote messages are discarded. These API errors never cause automatic retries.
159
+ Deletion `status` readers preserve nonnegative integer markers as well as booleans;
160
+ live project/task deletes return integer markers, whose meaning is not inferred.
161
+
162
+ `authentication.authorized_user`, `users.find_by_email(email)`, and activity's `files`,
163
+ `attach_files(activity_id:, files:)`, `delete_file(id)` and `download_file(id)` wrap
164
+ verified POST bindings. Attachments preserve the WSDL FileList representation.
165
+ Live upload verification uses `files: { "items" => [{ "name" => "example.txt",
166
+ "type" => "file", "content" => [bytes].pack("m0"), "size" => bytes.bytesize }] }`:
167
+ the caller supplies base64 content explicitly. Downloads return a `FivePM::Download`
168
+ in both response modes, with immutable binary `bytes`, optional `content_type`, and
169
+ redacted inspection. They use the authenticated POST endpoint, share the HTTP response
170
+ size limit, and never follow returned URLs. The live endpoint returns bytes despite
171
+ the JSON option. Missing-file API error envelopes are raised rather than returned as files.
172
+
173
+ User creation requires a symbolic `securityLevel` such as `"user"` in the tested
174
+ account, even though returned records use numeric strings and the help reference
175
+ lists numeric role codes. Request values are not silently translated.
176
+
177
+ Request validation rejects missing required operation parameters, unsafe nested field
178
+ names, arbitrary Ruby objects, cycles, invalid identifiers and invalid pagination bounds
179
+ before authentication or I/O. Typed records can be passed to create/update helpers using
180
+ their original JSON fields. Model list `to_h` retains the JSON view despite Enumerable.
181
+ Clients copy configuration strings and refuse Marshal serialization of credentials.
182
+
183
+ Run `bin/package` to verify the actual built artifact. See
184
+ [release preparation](docs/releasing.md) and [implementation guideposts](docs/implementation-guides.md).
185
+
186
+ ## Remaining release gates
187
+
188
+ - Review and merge the offline-tested source; live API refreshes are optional and owner-controlled.
189
+ - Keep the supported Ruby matrix passing in hosted CI. Live checks remain opt-in and outside CI.
190
+ - Date/time strings remain opaque; no timezone conversion or consistent-snapshot guarantee is offered.
191
+ - Rate-limit and transient-network behavior is tested synthetically, without stressing the live account.
192
+ - Configure the protected `release` environment and both RubyGems trusted publishers
193
+ using [release preparation](docs/releasing.md); verify name availability and obtain
194
+ explicit approval for the version and exact commit before dispatching publication.
195
+
196
+ Official sources: [developer guide](https://www.5pmweb.com/5pm_api.php),
197
+ [API reference](https://www.5pmweb.com/help/api_docs.php).
198
+
199
+ ## Standalone MCP adapter
200
+
201
+ The separate [5pm-mcp package](mcp/README.md) provides ten read tools over the
202
+ SDK using the official MCP Ruby SDK. Its executable is read-only; optional embedded
203
+ project/task mutation tools require trusted per-call operator approval. MCP is a
204
+ runtime dependency only of that separate gem. Run `bundle exec five-pm-mcp` with
205
+ the same injected environment variables, or see its README for host configuration.
206
+
207
+ `bin/package-mcp` verifies both installed artifacts and the adapter
208
+ protocol in an isolated temporary GEM_HOME. `bin/live-check` adds opt-in disposable
209
+ account verification; no gem publication is automated.
data/SECURITY.md ADDED
@@ -0,0 +1,15 @@
1
+ # Security
2
+
3
+ Report vulnerabilities privately through GitHub private vulnerability reporting when enabled.
4
+ Do not put credentials or private account data into public issues.
5
+
6
+ The client uses HTTPS POST bodies, peer certificate verification, fixed account hostnames,
7
+ public IPv4 resolution pinned per connection, bounded timeouts, and no proxies or redirects.
8
+ Read retries are disabled by default and bounded by an explicit read-operation allowlist
9
+ when enabled. Authentication, mutations, API errors and schema failures are never replayed.
10
+ Sessions are held only in memory. Attachment downloads use the same bounded authenticated
11
+ transport; their inspection and MCP audit events never include binary contents.
12
+ Client and transport inspection redact state; callers must also avoid logging request bodies
13
+ or returned user records. Ruby memory is not a secure secret vault.
14
+
15
+ No Rails application is present; Brakeman does not apply.
@@ -0,0 +1,71 @@
1
+ # API v2 contract inventory
2
+
3
+ Source: public demo account WSDLs fetched over HTTPS on 2026-10-28.
4
+
5
+ These are upstream contracts, not response recordings. The account-specific WSDL/XSD
6
+ inventories were compared successfully on 2026-10-30. See [live validation](live-validation.md)
7
+ for JSON-envelope differences and sanitized regression fixtures.
8
+
9
+ | Service | Operation | POST request parts | Response parts |
10
+ | --- | --- | --- | --- |
11
+ | activity | add | sessionId: `xsd:string`, activity: `nsc:Activity` | activity: `nsc:Activity` |
12
+ | activity | update | sessionId: `xsd:string`, activity: `nsc:Activity` | activity: `nsc:Activity` |
13
+ | activity | remove | sessionId: `xsd:string`, id: `xsd:integer` | status: `xsd:boolean` |
14
+ | activity | getList | sessionId: `xsd:string`, filter: `nsc:ActivityListFilter`, offset: `xsd:integer`, count: `xsd:integer`, withFiles: `xsd:boolean`, withNames: `xsd:boolean` | list: `nsc:ActivityList` |
15
+ | activity | getFilesList | sessionId: `xsd:string`, filter: `nsc:ActivityListFilter`, offset: `xsd:integer`, count: `xsd:integer` | list: `nsc:FileList` |
16
+ | activity | getById | sessionId: `xsd:string`, id: `xsd:integer`, withNames: `xsd:boolean` | activity: `nsc:Activity` |
17
+ | activity | attachFiles | sessionId: `xsd:string`, activityId: `xsd:integer`, files: `nsc:FileList` | status: `xsd:boolean` |
18
+ | activity | removeFile | sessionId: `xsd:string`, id: `xsd:integer` | status: `xsd:boolean` |
19
+ | activity | downloadFile | sessionId: `xsd:string`, id: `xsd:integer`, stream: `xsd:boolean` | file: `nsc:File` |
20
+ | activity | getResizeImg | Declared message only; no POST binding | Unsupported |
21
+ | authentication | signIn | login: `xsd:string`, password: `xsd:string` | auth: `nsc:AuthToken` |
22
+ | authentication | signOut | sessionId: `xsd:string` | signOutState: `xsd:boolean` |
23
+ | authentication | getAuthorizedUser | sessionId: `xsd:string` | user: `nsc:User` |
24
+ | authentication | nop | sessionId: `xsd:string` | nopState: `xsd:boolean` |
25
+ | authentication | isSsl | | isSsl: `xsd:boolean` |
26
+ | projects | add | sessionId: `xsd:string`, project: `nsc:Project` | project: `nsc:Project` |
27
+ | projects | update | sessionId: `xsd:string`, project: `nsc:Project` | project: `nsc:Project` |
28
+ | projects | remove | sessionId: `xsd:string`, id: `xsd:integer` | status: `xsd:boolean` |
29
+ | projects | getById | sessionId: `xsd:string`, id: `xsd:integer` | project: `nsc:Project` |
30
+ | projects | getAll | sessionId: `xsd:string`, loadTeam: `xsd:boolean` | list: `nsc:ProjectList` |
31
+ | projects | getList | sessionId: `xsd:string`, filter: `nsc:ProjectListFilter`, offset: `xsd:integer`, count: `xsd:integer`, loadTeam: `xsd:boolean` | list: `nsc:ProjectList` |
32
+ | projectsgroups | add | sessionId: `xsd:string`, group: `nsc:ProjectsGroup` | group: `nsc:ProjectsGroup` |
33
+ | projectsgroups | update | sessionId: `xsd:string`, group: `nsc:ProjectsGroup` | group: `nsc:ProjectsGroup` |
34
+ | projectsgroups | remove | sessionId: `xsd:string`, id: `xsd:integer` | status: `xsd:boolean` |
35
+ | projectsgroups | getById | sessionId: `xsd:string`, id: `xsd:integer` | group: `nsc:ProjectsGroup` |
36
+ | projectsgroups | getAll | sessionId: `xsd:string` | list: `nsc:ProjectsGroupList` |
37
+ | tasks | add | sessionId: `xsd:string`, task: `nsc:Task` | task: `nsc:Task` |
38
+ | tasks | update | sessionId: `xsd:string`, task: `nsc:Task` | task: `nsc:Task` |
39
+ | tasks | remove | sessionId: `xsd:string`, id: `xsd:integer` | status: `xsd:boolean` |
40
+ | tasks | getById | sessionId: `xsd:string`, id: `xsd:integer` | task: `nsc:Task` |
41
+ | tasks | getList | sessionId: `xsd:string`, filter: `nsc:TaskListFilter`, offset: `xsd:integer`, count: `xsd:integer`, loadTeam: `xsd:boolean` | list: `nsc:TaskList` |
42
+ | users | add | sessionId: `xsd:string`, user: `nsc:User` | user: `nsc:User` |
43
+ | users | update | sessionId: `xsd:string`, user: `nsc:User` | user: `nsc:User` |
44
+ | users | remove | sessionId: `xsd:string`, id: `xsd:integer` | status: `xsd:boolean` |
45
+ | users | getById | sessionId: `xsd:string`, id: `xsd:integer` | user: `nsc:User` |
46
+ | users | getByEmail | sessionId: `xsd:string`, email: `xsd:string` | user: `nsc:User` |
47
+ | users | getAll | sessionId: `xsd:string` | sessionId: `xsd:string`, list: `nsc:UserList` |
48
+ | users | getAuthorizedUser | sessionId: `xsd:string` | user: `nsc:User` |
49
+
50
+ ## Known discrepancies and limits
51
+
52
+ - Activity getResizeImg has request/response messages but no POST binding; it is deliberately excluded from callable operations.
53
+ - The schema declares AuthToken twice; do not assume strict XSD validation succeeds.
54
+ - Help reference contains GET/POST path inconsistencies. Use WSDL POST bindings.
55
+ - Activity getList help text calls its result GroupList; WSDL declares ActivityList.
56
+ - Users getAll declares sessionId alongside list in its response. Preserve the whole response.
57
+ - HTTP-200 numeric API errors, data wrappers and binary downloads are covered by live checks.
58
+ - Pagination and read retries are bounded opt-ins. There is no date/time conversion or URL fetching.
59
+ - No credentials were used to fetch these contracts.
60
+
61
+ Sources: https://www.5pmweb.com/5pm_api.php and https://www.5pmweb.com/help/api_docs.php
62
+
63
+ ## Snapshot checksums
64
+
65
+ - `activity.wsdl`: `381ef30b11b407778e9d899b4aa77b4d8b53b99d116fafd515f1f5ea33753fcc`
66
+ - `authentication.wsdl`: `e5e408bd08ad40f1daa0d65038975aa2e8230a39d0ba25f126995a5173ace132`
67
+ - `projects.wsdl`: `4686919f3a5ec647a5d9ed067e2c85bd93065ea43a25bae52326e37582975486`
68
+ - `projectsgroups.wsdl`: `ade39f53b9be0aa081221a7dbc1fe63bdd7317e6c3c1e8bdca4dfa993c852604`
69
+ - `schema.xsd`: `aa938cd35e979479b3559bbd04327fc9d91c3ce5ac860635e4465aa2a65827a8`
70
+ - `tasks.wsdl`: `d8cabc28841b33515f9c23ab5d1ca9f5975b486f173c98bcde5026f05a59480f`
71
+ - `users.wsdl`: `a660022447f04d7c32e92768eb77e8e6568b56bc775bd9f3c66152b1227d9bdc`
@@ -0,0 +1,58 @@
1
+ <!-- docs/cached-validation.md -->
2
+ # Keychain and cached validation
3
+
4
+ Run these commands from the checkout on macOS. The setup prompts for the disposable
5
+ tenant's hostname (without scheme/path), standard User login, and password. Each is
6
+ stored in the login Keychain under `5pm-ruby.validation.host`, `.login`, or `.password`,
7
+ with account `5pm-ruby`. Keychain may ask for access permission. Values never appear
8
+ in command arguments, repository files, output, or the cached report.
9
+
10
+ ```sh
11
+ bin/validate --setup
12
+ ```
13
+
14
+ Commit the reviewed source changes before validating. On the first run, explicitly
15
+ designate the tenant as disposable. This runs the existing read-only live harness,
16
+ including WSDL/XSD contract checks and session cleanup. It does not run CRUD tests.
17
+
18
+ ```sh
19
+ bin/validate --disposable
20
+ ```
21
+
22
+ Later runs reuse the cached evidence without Keychain access or network requests.
23
+ There is no timer, polling, expiration, or automatic refresh:
24
+
25
+ ```sh
26
+ bin/validate
27
+ ```
28
+
29
+ After a known 5PM API update, deliberately replace the cache:
30
+
31
+ ```sh
32
+ bin/validate --refresh --disposable
33
+ ```
34
+
35
+ Changes to SDK code, adapter code, bin commands or validation helpers set
36
+ `source_matches: false` on a cached result. The old report remains usable as
37
+ historical evidence, with its original commit and fingerprint. Source changes never
38
+ force a live refresh. Failed refreshes preserve the previous file. Updating
39
+ credentials does not refresh evidence.
40
+ Use a distinct `--service NAME` per tenant; that also selects a separate default cache.
41
+
42
+ The default cache is `~/Library/Caches/5pm-ruby/<service-sha256>.json`, outside the
43
+ repository, with mode 0600. `--cache FILE` selects a different local destination.
44
+ It stores only the validation timestamp, tested commit, source fingerprint and scope.
45
+ It excludes remote responses, tenant details, tokens, credentials and record IDs.
46
+
47
+ Cached contract/read evidence has `release_evidence: false` and
48
+ `mutations_verified: false`. It is reusable development evidence, not fabricated
49
+ a fresh validation of changed source. Live evidence is optional for releases:
50
+ Stan decides when a refresh is needed. No active account or subscription is required
51
+ to run offline tests, package checks or release preflight.
52
+
53
+ See [live validation](live-validation.md) and [release preparation](releasing.md).
54
+
55
+ —
56
+ Stan Carver II
57
+ Made in Texas 🤠
58
+ https://stancarver.com
@@ -0,0 +1,12 @@
1
+ # Implementation guideposts
2
+
3
+ Reviewed these user-owned repositories as design guides, without importing their
4
+ service-specific behavior or adding their dependencies to the SDK:
5
+
6
+ - [printavo-ruby client](https://github.com/scarver2/printavo-ruby/blob/464ae6f29b8ff7627cf829c02351d1b9eadea37d/lib/printavo/client.rb): independent clients, resource helpers, explicit configuration.
7
+ - [printavo-ruby connection](https://github.com/scarver2/printavo-ruby/blob/464ae6f29b8ff7627cf829c02351d1b9eadea37d/lib/printavo/connection.rb): configurable retry controls; FivePM restricts retries further to an explicit read-operation allowlist.
8
+ - [abbu packaging](https://github.com/scarver2/abbu/blob/4c64d0eb0ccdbc0a8c04b2e50da92594faf75d09/bin/package): verify the built artifact independently of its source checkout.
9
+ - [abbu release preflight](https://github.com/scarver2/abbu/blob/4c64d0eb0ccdbc0a8c04b2e50da92594faf75d09/bin/release-check): inspect accepted commits without executing candidate code; separate preparation from publication authorization.
10
+ - [abbu MCP adapter](https://github.com/scarver2/abbu/blob/4c64d0eb0ccdbc0a8c04b2e50da92594faf75d09/lib/abbu/mcp/server.rb): use the official SDK for protocol handling, allowlist tools and redact errors.
11
+
12
+ The public 5pm WSDL/XSD remains the source for 5pm-specific operations and types.
@@ -0,0 +1,45 @@
1
+ # Issue #1 implementation status
2
+
3
+ Initial implementation, 2026-10-28. Worktree: `sdk`, branch: `feat/api-v2-sdk`.
4
+
5
+ ## Implemented
6
+
7
+ - MIT gem scaffold with Ruby 3.2 minimum, explicit runtime dependencies and checksum lockfile.
8
+ - Snapshots of all six public demo service WSDLs and schema; 37 callable POST operations.
9
+ - Complete request/response part inventory with source discrepancies documented.
10
+ - Six services, five resource CRUD/list helpers, generic calls limited to verified POST bindings.
11
+ - HTTPS form POST, public IPv4 pinning, fixed account domains, timeouts, no redirects/proxies and read retries disabled by default.
12
+ - In-memory synchronized sessions with 30-minute TTL and conservative refresh margin.
13
+ - Error classes that omit remote bodies, network details and chained secret-bearing exceptions.
14
+ - RSpec synthetic tests, WSDL contract checks, SimpleCov 90% minimum, RuboCop, CI and Dependabot.
15
+ - Packaging validation, quickstart, contribution/security policies and separate MCP adapter plan.
16
+ - Opt-in typed response models for all 14 schema response types; unknown fields and raw JSON preserved.
17
+ - Nested typed lists, immutable snapshots, snake_case readers and redacted inspection.
18
+
19
+ - Bounded optional read retries/pagination, streamed body limits, request validation and explicit attachment helpers.
20
+ - Isolated package verification, read-only release gate and opt-in read-only sandbox preflight.
21
+ - Reviewed scarver2/abbu and scarver2/printavo-ruby patterns; see implementation-guides.md.
22
+
23
+ - Separate 5pm-mcp gem with official SDK, read-only stdio tools, opt-in host-confirmed mutations and redacted audit events.
24
+ - Real synthetic stdio/executable tests and isolated installation verification for both gem artifacts.
25
+
26
+ ## Verification completed on 2026-10-30
27
+
28
+ - Offline schema, protocol, redaction, binary transport and mutation-safety regression tests.
29
+ - RuboCop, strict builds, core dependency isolation and installed-package checks.
30
+ - Live account-specific WSDL/schema comparison, all resource CRUD/read paths,
31
+ pagination, user creation and synthetic-attachment byte round trips.
32
+ - Live MCP stdio reads and embedded project/task mutation approvals, denials and audits.
33
+ - Supported Ruby matrix: 3.2, 3.3, 3.4 and 4.0.
34
+
35
+ See [live validation](live-validation.md) for reproducible commands and API differences.
36
+ The development report identifies the tested working tree; it does not authorize a release.
37
+
38
+ ## Remaining release gates
39
+
40
+ - Review and merge the tested source with passing hosted CI.
41
+ - Generate accepted-commit release evidence according to [releasing](releasing.md).
42
+ - Recheck RubyGems name availability and obtain explicit publication approval.
43
+
44
+ Date/time conversion is intentionally absent. Retry/rate-limit failure paths are
45
+ verified synthetically rather than by stressing the live account.