anchor.diff:  
   -behaviour(chef_object).
   
  +-spec new_record(OrgId :: object_id(),
  +                 AuthzId :: object_id() | unset,
  +                 ObjectEjson :: ejson_term()) ->
  +                        #chef_node{}.
   new_record(OrgId, AuthzId, NodeData) ->
       Name = ej:get({<<"name">>}, NodeData),
       Environment = ej:get({<<"chef_environment">>}, NodeData),
  
positive.code:          name/1,
           new_record/3,
           parse_check_binary_as_json_node/2,
           type_name/1,
           validate_json_node/2
          ]).
  
  -ifdef(TEST).
  
negative.code:                environment = Environment,
                 serialized_object = Data}.
  
  -spec name(#chef_node{}) -> binary().
  name(#chef_node{name = Name}) ->
      Name.
  
  -spec id(#chef_node{}) -> object_id().
  id(#chef_node{id = Id}) ->
      Id.
  
  -spec type_name(#chef_node{}) -> node.
  type_name(#chef_node{}) ->
      node.
  
  -spec authz_id(#chef_node{}) -> object_id().
  authz_id(#chef_node{authz_id = AuthzId}) ->
      AuthzId.
  
  -spec ejson_for_indexing(#chef_node{}, ejson_term()) -> ejson_term().
  ejson_for_indexing(#chef_node{name = Name, environment = Environment}, Node) ->
      Defaults = ej:get({<<"default">>}, Node, ?EMPTY_EJSON_HASH),
      Normal = ej:get({<<"normal">>}, Node, ?EMPTY_EJSON_HASH),
      Override = ej:get({<<"override">>}, Node, ?EMPTY_EJSON_HASH),
      %% automatic may not always be present
      Automatic = ej:get({<<"automatic">>}, Node, ?EMPTY_EJSON_HASH),
      DefaultNormal = chef_deep_merge:merge(Defaults, Normal),
      DefaultNormalOverride = chef_deep_merge:merge(DefaultNormal, Override),
      {Merged} = chef_deep_merge:merge(DefaultNormalOverride, Automatic),
      RunList = ej:get({<<"run_list">>}, Node, []),
      %% We transform to a dict to ensure we override the top-level keys
      %% with the appropriate values and don't introduce any duplicate
      %% keys
      NodeDict = dict:from_list(Merged),
      TopLevelDict = dict:from_list([{<<"name">>, Name},
                                     {<<"chef_type">>, <<"node">>},
                                     %% FIXME: nodes may have environment in the db, but not in JSON
                                     %% or not set at all (pre-environments nodes).
                                     {<<"chef_environment">>, Environment},
                                     {<<"recipe">>, extract_recipes(RunList)},
                                     {<<"role">>, extract_roles(RunList)},
                                     {<<"run_list">>, RunList}]),
      NodeDict1 = dict:merge(fun(_Key, TopVal, _AttrVal) ->
                                     TopVal
                             end, TopLevelDict, NodeDict),
      {dict:to_list(NodeDict1)}.
  
  -spec update_from_ejson(#chef_node{}, ejson_term()) -> #chef_node{}.
  update_from_ejson(#chef_node{} = Node, NodeJson) ->
      Name = ej:get({<<"name">>}, NodeJson),
      %% We expect that the insert_autofill_fields call will insert default when necessary
      Environment = ej:get({<<"chef_environment">>}, NodeJson),
      Data = chef_db_compression:compress(chef_node, chef_json:encode(NodeJson)),
      Node#chef_node{name = Name, environment = Environment, serialized_object = Data}.
  
  -spec set_created(#chef_node{}, object_id()) -> #chef_node{}.
  set_created(#chef_node{} = Object, ActorId) ->
      Now = chef_object_base:sql_date(now),
      Object#chef_node{created_at = Now, updated_at = Now, last_updated_by = ActorId}.
  
  -spec set_updated(#chef_node{}, object_id()) -> #chef_node{}.
  set_updated(#chef_node{} = Object, ActorId) ->
      Now = chef_object_base:sql_date(now),
      Object#chef_node{updated_at = Now, last_updated_by = ActorId}.
  
  bulk_get_query() ->
      bulk_get_nodes.
  
  create_query() ->
      insert_node.
  
  delete_query() ->
      delete_node_by_id.
  
  find_query() ->
      find_node_by_orgid_name.
  
  list_query() ->
      list_nodes_for_org.
  
  update_query() ->
      update_node_by_id.
  
  extract_recipes(RunList) ->
      [ binary:part(Item, {0, byte_size(Item) - 1})
  