jpie 3.5.0 → 3.7.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: c54015e01586dd3a4a6f14430928d231e1e6ce8f1e46a9cefa54ab7c93d45226
4
- data.tar.gz: 90f24a271244366daf7ff47dd7a20a79740baf72c037b4f9ff428bd7a69bde33
3
+ metadata.gz: 7afd23e7e9ffb26be73d59ec5776b05d0a1375edc172c8faf8ee8675c1a9e681
4
+ data.tar.gz: d0b2ac2e8bb3035829524faabc24d2eb787cb0f716e66e3f008043b42a3c31fb
5
5
  SHA512:
6
- metadata.gz: bd1e8f0ce88654ca8c594f9b58cb4320ecfeba576d5f21062e0136737c0bbabd24a3aef94f899ad0732a7f363f9115e1e602c9079b930aa7159187b90e834a61
7
- data.tar.gz: ba4d97f2a60f0b73ca1480de68cdfb05c992e2434bff55ee6569da74447788c7f1c51e7ac2047126976adca6ba1a88a9b8c4fd6e9e60a7b1cddd7aa26ac379e7
6
+ metadata.gz: de7034c8a695bb8a27501f8c00129a8b4465c3ac7963da3a3327c634b5c85064b29dfbd232841a27a84e15c1b2ef477e1ecf24bae4752ab704f8b76919dba806
7
+ data.tar.gz: b3474c65fcd26816af5f8f02766f8a52e850112919e29f14b1deff31f5bbc785bbf12dd24d331af4cc321f086f45c06d7ed47b068fd90158d3765b0eda5a631f
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: writing-discipline
3
+ description: The writing standard for all prose — chat responses, documentation, code comments, commit messages, PR descriptions, Jira and Slack posts. Use BEFORE you write any human-facing text. Enforces ASD-STE100 (Simplified Technical English) plus Orwell's six rules — active voice, short sentences, short everyday words, one instruction per sentence, no idioms, no jargon, cut every word you can.
4
+ ---
5
+
6
+ # Writing discipline
7
+
8
+ All prose obeys ASD-STE100 (Simplified Technical English) and Orwell's six rules of
9
+ writing. This covers chat responses, documentation, code comments, commit messages,
10
+ PR descriptions, and Jira and Slack posts.
11
+
12
+ ## ASD-STE100
13
+
14
+ - Use only approved words from the STE dictionary (one word, one meaning). Technical
15
+ names and technical verbs of this domain are permitted.
16
+ - Write in the active voice. Use the present tense where possible.
17
+ - Keep sentences short: maximum 20 words in an instruction, maximum 25 words in
18
+ descriptive text.
19
+ - Give one instruction per sentence. Start an instruction with the command form of
20
+ the verb.
21
+ - Do not make noun clusters of more than three nouns.
22
+ - Do not use slang, idioms, or Latin abbreviations (no "e.g.", "i.e.", "etc.", "via").
23
+ - Use a vertical list when you give more than three facts or steps in sequence.
24
+ - Start a warning or caution with the command, not the explanation.
25
+
26
+ ## Orwell's six rules
27
+
28
+ 1. Never use a metaphor, simile, or other figure of speech which you are used to
29
+ seeing in print.
30
+ 2. Never use a long word where a short one will do.
31
+ 3. If it is possible to cut a word out, always cut it out.
32
+ 4. Never use the passive where you can use the active.
33
+ 5. Never use a foreign phrase, a scientific word, or a jargon word if you can think
34
+ of an everyday English equivalent.
35
+ 6. Break any of these rules sooner than say anything outright barbarous.
36
+
37
+ ## How to apply
38
+
39
+ - Draft, then cut. Remove every word that does not change the meaning.
40
+ - Break a long sentence at "which", "and", or "but".
41
+ - Prefer a verb to a noun cluster: write "when the job runs", not "at job execution
42
+ time".
43
+ - Replace idioms with plain statements: write "this shows the cause", not "this is
44
+ the smoking gun".
45
+ - Replace Latin abbreviations: write "for example", not "e.g."; name the items, or
46
+ write "and more", instead of "etc.".
47
+ - Keep technical names, commands, and code identifiers as they are. The standard
48
+ limits prose, not identifiers.
49
+
50
+ ## Self-check before you send
51
+
52
+ - Is every sentence inside the length limit?
53
+ - Is every verb active?
54
+ - Can any word go?
55
+ - Does a reader who is not an expert know every word that is not a technical name?
data/CLAUDE.md ADDED
@@ -0,0 +1,34 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Writing standard: ASD-STE100 + Orwell's six rules
6
+
7
+ All prose you write — chat responses, documentation, code comments, commit messages,
8
+ PR descriptions, Jira and Slack posts — must obey ASD-STE100 (Simplified Technical
9
+ English) and Orwell's six rules of writing.
10
+
11
+ ASD-STE100:
12
+
13
+ - Use only approved words from the STE dictionary (one word, one meaning). Technical
14
+ names and technical verbs of this domain are permitted.
15
+ - Write in the active voice. Use the present tense where possible.
16
+ - Keep sentences short: maximum 20 words in an instruction, maximum 25 words in
17
+ descriptive text.
18
+ - Give one instruction per sentence. Start an instruction with the command form of
19
+ the verb.
20
+ - Do not make noun clusters of more than three nouns.
21
+ - Do not use slang, idioms, or Latin abbreviations (no "e.g.", "i.e.", "etc.", "via").
22
+ - Use a vertical list when you give more than three facts or steps in sequence.
23
+ - Start a warning or caution with the command, not the explanation.
24
+
25
+ Orwell's six rules:
26
+
27
+ 1. Never use a metaphor, simile, or other figure of speech which you are used to
28
+ seeing in print.
29
+ 2. Never use a long word where a short one will do.
30
+ 3. If it is possible to cut a word out, always cut it out.
31
+ 4. Never use the passive where you can use the active.
32
+ 5. Never use a foreign phrase, a scientific word, or a jargon word if you can think
33
+ of an everyday English equivalent.
34
+ 6. Break any of these rules sooner than say anything outright barbarous.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- jpie (3.1.6)
4
+ jpie (3.6.0)
5
5
  actionpack (~> 8.1, >= 8.1.0)
6
6
  pg_query (>= 4)
7
7
  prosopite (>= 1)
@@ -7,7 +7,7 @@ module JSONAPI
7
7
  include IncludePreloading
8
8
 
9
9
  def serialize_resource(resource)
10
- JSONAPI::Serializer.new(resource).to_hash(
10
+ JSONAPI::Serializer.new(resource, authorization_context: self).to_hash(
11
11
  include: parse_include_param,
12
12
  fields: parse_fields_param,
13
13
  document_meta: jsonapi_document_meta,
@@ -42,7 +42,8 @@ module JSONAPI
42
42
  end
43
43
 
44
44
  def serialize_single(resource, includes, fields)
45
- JSONAPI::Serializer.new(resource).to_hash(include: includes, fields:, document_meta: nil)
45
+ JSONAPI::Serializer.new(resource, authorization_context: self).to_hash(include: includes, fields:,
46
+ document_meta: nil,)
46
47
  end
47
48
 
48
49
  def collect_included(result, all_included, processed)
@@ -7,10 +7,22 @@ module JSONAPI
7
7
  loaded_array = association.target.respond_to?(:to_a) ? association.target.to_a : Array(association.target)
8
8
  return [] if loaded_array.empty?
9
9
 
10
- resource_scope = ResourceLoader.find_for_model(related_klass).records
11
- return loaded_array if resource_scope.where_clause.empty?
10
+ base_scope = ResourceLoader.find_for_model(related_klass).records
11
+ authorized_scope = apply_include_authorization(base_scope, related_klass)
12
+
13
+ # When the authorization_scope hook narrows the relation (joins/subqueries a
14
+ # where-hash can't express), the eager-loaded set — preloaded through acts_as_tenant
15
+ # alone — must be re-checked against the authorized scope in the database, so a
16
+ # relationship never surfaces a record the related endpoint denies. An unchanged
17
+ # scope keeps the in-memory, query-free records-only filtering.
18
+ return filter_by_query(loaded_array, authorized_scope) if narrowed?(base_scope, authorized_scope)
19
+ return loaded_array if base_scope.where_clause.empty?
20
+
21
+ filter_by_where_hash(loaded_array, base_scope, related_klass)
22
+ end
12
23
 
13
- filter_by_where_hash(loaded_array, resource_scope, related_klass)
24
+ def narrowed?(base_scope, authorized_scope)
25
+ authorized_scope.to_sql != base_scope.to_sql
14
26
  end
15
27
 
16
28
  def filter_by_where_hash(loaded_array, resource_scope, related_klass)
@@ -96,7 +96,23 @@ module JSONAPI
96
96
 
97
97
  def build_scoped_relation(related_klass, association)
98
98
  related_base_scope = ResourceLoader.find_for_model(related_klass).records
99
- association.scope.merge(related_base_scope)
99
+ association.scope.merge(apply_include_authorization(related_base_scope, related_klass))
100
+ end
101
+
102
+ # A sideloaded `?include=` is loaded through acts_as_tenant alone, bypassing the
103
+ # related resource's authorization_scope — so a relationship can reach records the
104
+ # related resource's own collection endpoint denies (e.g. an include that surfaces
105
+ # rows a Pundit scope hides). Route the related resource's base records through the
106
+ # configured authorization_scope hook, exactly as the primary collection does, so an
107
+ # included record can never escape the reader's authorization. No context (serializer
108
+ # used outside a request) or no configured hook leaves the scope untouched.
109
+ def apply_include_authorization(scope, model_class)
110
+ return scope unless authorization_context
111
+
112
+ handler = JSONAPI.configuration.authorization_scope
113
+ return scope unless handler
114
+
115
+ handler.call(controller: authorization_context, scope:, action: :index, model_class:)
100
116
  end
101
117
 
102
118
  def get_active_storage_records(current_record, association_name)
@@ -111,7 +127,8 @@ module JSONAPI
111
127
  return if ctx.processed.include?(build_record_key(related_record))
112
128
 
113
129
  requested = include_paths_to_relationship_names(ctx.all_includes, path_to_record)
114
- serializer = self.class.new(related_record, parent_record:, association_name:)
130
+ serializer = self.class.new(related_record, parent_record:, association_name:,
131
+ authorization_context:,)
115
132
  ctx.included_records << serializer.serialize_record(ctx.fields, requested_relationships: requested)
116
133
  ctx.processed.add(build_record_key(related_record))
117
134
  end
@@ -34,12 +34,14 @@ module JSONAPI
34
34
  @jsonapi_object = nil
35
35
  end
36
36
 
37
- def initialize(record, definition: nil, base_definition: nil, parent_record: nil, association_name: nil)
37
+ def initialize(record, definition: nil, base_definition: nil, parent_record: nil, association_name: nil,
38
+ authorization_context: nil)
38
39
  @record = record
39
40
  @definition = definition || ResourceLoader.find_for_model(record.class)
40
41
  @base_definition = base_definition
41
42
  @parent_record = parent_record
42
43
  @association_name = association_name
44
+ @authorization_context = authorization_context
43
45
  @sti_subclass = nil
44
46
  end
45
47
 
@@ -67,7 +69,7 @@ module JSONAPI
67
69
 
68
70
  private
69
71
 
70
- attr_reader :record, :definition, :parent_record, :association_name
72
+ attr_reader :record, :definition, :parent_record, :association_name, :authorization_context
71
73
 
72
74
  def base_definition
73
75
  @base_definition ||= definition
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSONAPI
4
- VERSION = "3.5.0"
4
+ VERSION = "3.7.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jpie
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.0
4
+ version: 3.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emil Kampp
@@ -88,6 +88,7 @@ files:
88
88
  - ".claude/skills/nasa-power-of-ten-ruby/SKILL.md"
89
89
  - ".claude/skills/root-cause-analysis/SKILL.md"
90
90
  - ".claude/skills/skills-and-subagents/SKILL.md"
91
+ - ".claude/skills/writing-discipline/SKILL.md"
91
92
  - ".cursor/agents/bias-reviewer.md"
92
93
  - ".cursor/agents/lint-format.md"
93
94
  - ".cursor/agents/nasa-power-of-ten-reviewer.md"
@@ -97,6 +98,7 @@ files:
97
98
  - ".gitignore"
98
99
  - ".rspec"
99
100
  - ".rubocop.yml"
101
+ - CLAUDE.md
100
102
  - Gemfile
101
103
  - Gemfile.lock
102
104
  - PERFORMANCE_BASELINE.md